home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / share / perl5 / Immunix / SubDomain.pm < prev   
Text File  |  2008-10-08  |  216KB  |  6,629 lines

  1. # $Id: SubDomain.pm 1273 2008-06-03 22:54:55Z jrjohansen $
  2. #
  3. # ----------------------------------------------------------------------
  4. #    Copyright (c) 2006 Novell, Inc. All Rights Reserved.
  5. #
  6. #    This program is free software; you can redistribute it and/or
  7. #    modify it under the terms of version 2 of the GNU General Public
  8. #    License as published by the Free Software Foundation.
  9. #
  10. #    This program is distributed in the hope that it will be useful,
  11. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #    GNU General Public License for more details.
  14. #
  15. #    You should have received a copy of the GNU General Public License
  16. #    along with this program; if not, contact Novell, Inc.
  17. #
  18. #    To contact Novell about this file by physical or electronic mail,
  19. #    you may find current contact information at www.novell.com.
  20. # ----------------------------------------------------------------------
  21.  
  22. package Immunix::SubDomain;
  23.  
  24. use strict;
  25. use warnings;
  26.  
  27. use Carp;
  28. use Cwd qw(cwd realpath);
  29. use File::Basename;
  30. use File::Temp qw/ tempfile tempdir /;
  31. use Data::Dumper;
  32.  
  33. use Locale::gettext;
  34. use POSIX;
  35. use Storable qw(dclone);
  36.  
  37. use Term::ReadKey;
  38.  
  39. use Immunix::Severity;
  40. use Immunix::Repository;
  41. use Immunix::Config;
  42. use LibAppArmor;
  43.  
  44. require Exporter;
  45. our @ISA    = qw(Exporter);
  46. our @EXPORT = qw(
  47.     %sd
  48.     %qualifiers
  49.     %include
  50.     %helpers
  51.  
  52.     $filename
  53.     $profiledir
  54.     $parser
  55.     $logger
  56.     $UI_Mode
  57.     $running_under_genprof
  58.  
  59.     which
  60.     getprofilefilename
  61.     get_full_path
  62.     fatal_error
  63.     get_pager
  64.  
  65.     getprofileflags
  66.     setprofileflags
  67.     complain
  68.     enforce
  69.  
  70.     autodep
  71.     reload
  72.  
  73.     UI_GetString
  74.     UI_GetFile
  75.     UI_YesNo
  76.     UI_ShortMessage
  77.     UI_LongMessage
  78.  
  79.     UI_Important
  80.     UI_Info
  81.     UI_PromptUser
  82.     display_changes
  83.     getkey
  84.  
  85.     do_logprof_pass
  86.  
  87.     loadincludes
  88.     readprofile
  89.     readprofiles
  90.     writeprofile
  91.     serialize_profile
  92.     attach_profile_data
  93.     parse_repo_profile
  94.     activate_repo_profiles
  95.  
  96.     check_for_subdomain
  97.  
  98.     setup_yast
  99.     shutdown_yast
  100.     GetDataFromYast
  101.     SendDataToYast
  102.  
  103.     checkProfileSyntax
  104.     checkIncludeSyntax
  105.     check_qualifiers
  106.  
  107.     isSkippableFile
  108. );
  109.  
  110. our $confdir = "/etc/apparmor";
  111.  
  112. our $running_under_genprof = 0;
  113.  
  114. our $DEBUGGING;
  115.  
  116. our $unimplemented_warning = 0;
  117.  
  118. # keep track of if we're running under yast or not - default to text mode
  119. our $UI_Mode = "text";
  120.  
  121. our $sevdb;
  122.  
  123. # initialize Term::ReadLine if it's available
  124. our $term;
  125. eval {
  126.     require Term::ReadLine;
  127.     import Term::ReadLine;
  128.     $term = new Term::ReadLine 'AppArmor';
  129. };
  130.  
  131. # initialize the local poo
  132. setlocale(LC_MESSAGES, "")
  133.     unless defined(LC_MESSAGES);
  134. textdomain("apparmor-utils");
  135.  
  136. # where do we get our log messages from?
  137. our $filename;
  138.  
  139. our $cfg;
  140. our $repo_cfg;
  141.  
  142. our $parser;
  143. our $ldd;
  144. our $logger;
  145. our $profiledir;
  146. our $extraprofiledir;
  147.  
  148. # we keep track of the included profile fragments with %include
  149. my %include;
  150.  
  151. my %existing_profiles;
  152.  
  153. our $seenevents = 0;
  154.  
  155.  
  156. # these are globs that the user specifically entered.  we'll keep track of
  157. # them so that if one later matches, we'll suggest it again.
  158. our @userglobs;
  159.  
  160. ### THESE VARIABLES ARE USED WITHIN LOGPROF
  161. our %t;
  162. our %transitions;
  163. our %sd;    # we keep track of the original profiles in %sd
  164. our %original_sd;
  165. our %extras;  # inactive profiles from extras
  166.  
  167. my @log;
  168. my %pid;
  169.  
  170. my %seen;
  171. my %profilechanges;
  172. my %prelog;
  173. my %log;
  174. my %changed;
  175. my @created;
  176. my %skip;
  177. our %helpers;    # we want to preserve this one between passes
  178.  
  179. ### THESE VARIABLES ARE USED WITHIN LOGPROF
  180.  
  181. my %filelist;   # file level stuff including variables in config files
  182.  
  183. my $AA_MAY_EXEC = 1;
  184. my $AA_MAY_WRITE = 2;
  185. my $AA_MAY_READ = 4;
  186. my $AA_MAY_APPEND = 8;
  187. my $AA_MAY_LINK = 16;
  188. my $AA_MAY_LOCK = 32;
  189. my $AA_EXEC_MMAP = 64;
  190. my $AA_EXEC_UNSAFE = 128;
  191. my $AA_EXEC_INHERIT = 256;
  192. my $AA_EXEC_UNCONFINED = 512;
  193. my $AA_EXEC_PROFILE = 1024;
  194. my $AA_EXEC_CHILD = 2048;
  195. my $AA_EXEC_NT = 4096;
  196. my $AA_LINK_SUBSET = 8192;
  197.  
  198. my $AA_OTHER_SHIFT = 14;
  199. my $AA_USER_MASK = 16384 -1;
  200.  
  201. my $AA_EXEC_TYPE = $AA_MAY_EXEC | $AA_EXEC_UNSAFE | $AA_EXEC_INHERIT |
  202.             $AA_EXEC_UNCONFINED | $AA_EXEC_PROFILE | $AA_EXEC_CHILD | $AA_EXEC_NT;
  203.  
  204. my $ALL_AA_EXEC_TYPE = $AA_EXEC_TYPE;
  205.  
  206. my %MODE_HASH = (
  207.     x => $AA_MAY_EXEC,
  208.     X => $AA_MAY_EXEC,
  209.     w => $AA_MAY_WRITE,
  210.     W => $AA_MAY_WRITE,
  211.     r => $AA_MAY_READ,
  212.     R => $AA_MAY_READ,
  213.     a => $AA_MAY_APPEND,
  214.     A => $AA_MAY_APPEND,
  215.     l => $AA_MAY_LINK,
  216.     L => $AA_MAY_LINK,
  217.     k => $AA_MAY_LOCK,
  218.     K => $AA_MAY_LOCK,
  219.     m => $AA_EXEC_MMAP,
  220.     M => $AA_EXEC_MMAP,
  221. #   Unsafe => 128,
  222.     i => $AA_EXEC_INHERIT,
  223.     I => $AA_EXEC_INHERIT,
  224.     u => $AA_EXEC_UNCONFINED + $AA_EXEC_UNSAFE,        # U + Unsafe
  225.     U => $AA_EXEC_UNCONFINED,
  226.     p => $AA_EXEC_PROFILE + $AA_EXEC_UNSAFE,        # P + Unsafe
  227.     P => $AA_EXEC_PROFILE,
  228.     c => $AA_EXEC_CHILD + $AA_EXEC_UNSAFE,
  229.     C => $AA_EXEC_CHILD,
  230.     n => $AA_EXEC_NT + $AA_EXEC_UNSAFE,
  231.     N => $AA_EXEC_NT,
  232.     );
  233.  
  234. sub debug ($) {
  235.     my $message = shift;
  236.  
  237.     print DEBUG "$message\n" if $DEBUGGING;
  238. }
  239.  
  240. my %arrows = ( A => "UP", B => "DOWN", C => "RIGHT", D => "LEFT" );
  241.  
  242. sub getkey {
  243.     # change to raw mode
  244.     ReadMode(4);
  245.  
  246.     my $key = ReadKey(0);
  247.  
  248.     # decode arrow key control sequences
  249.     if ($key eq "\x1B") {
  250.         $key = ReadKey(0);
  251.         if ($key eq "[") {
  252.             $key = ReadKey(0);
  253.             if ($arrows{$key}) {
  254.                 $key = $arrows{$key};
  255.             }
  256.         }
  257.     }
  258.  
  259.     # return to cooked mode
  260.     ReadMode(0);
  261.     return $key;
  262. }
  263.  
  264. BEGIN {
  265.     # set things up to log extra info if they want...
  266.     if ($ENV{LOGPROF_DEBUG}) {
  267.         $DEBUGGING = 1;
  268.         open(DEBUG, ">/tmp/logprof_debug_$$.log");
  269.         my $oldfd = select(DEBUG);
  270.         $| = 1;
  271.         select($oldfd);
  272.     } else {
  273.         $DEBUGGING = 0;
  274.     }
  275. }
  276.  
  277. END {
  278.     $DEBUGGING && debug "Exiting...";
  279.  
  280.     # close the debug log if necessary
  281.     close(DEBUG) if $DEBUGGING;
  282. }
  283.  
  284. # returns true if the specified program contains references to LD_PRELOAD or
  285. # LD_LIBRARY_PATH to give the PX/UX code better suggestions
  286. sub check_for_LD_XXX ($) {
  287.     my $file = shift;
  288.  
  289.     return undef unless -f $file;
  290.  
  291.     # limit our checking to programs/scripts under 10k to speed things up a bit
  292.     my $size = -s $file;
  293.     return undef unless ($size && $size < 10000);
  294.  
  295.     my $found = undef;
  296.     if (open(F, $file)) {
  297.         while (<F>) {
  298.             $found = 1 if /LD_(PRELOAD|LIBRARY_PATH)/;
  299.         }
  300.         close(F);
  301.     }
  302.  
  303.     return $found;
  304. }
  305.  
  306. sub fatal_error ($) {
  307.     my $message = shift;
  308.  
  309.     my $details = "$message\n";
  310.  
  311.     if ($DEBUGGING) {
  312.  
  313.         # we'll include the stack backtrace if we're debugging...
  314.         $details = Carp::longmess($message);
  315.  
  316.         # write the error to the log
  317.         print DEBUG $details;
  318.     }
  319.  
  320.     # we'll just shoot ourselves in the head if it was one of the yast
  321.     # interface functions that ran into an error.  it gets really ugly if
  322.     # the yast frontend goes away and we try to notify the user of that
  323.     # problem by trying to send the yast frontend a pretty dialog box
  324.     my $caller = (caller(1))[3];
  325.  
  326.     exit 1 if defined($caller) && $caller =~ /::(Send|Get)Data(To|From)Yast$/;
  327.  
  328.     # tell the user what the hell happened
  329.     UI_Important($details);
  330.  
  331.     # make sure the frontend exits cleanly...
  332.     shutdown_yast();
  333.  
  334.     # die a horrible flaming death
  335.     exit 1;
  336. }
  337.  
  338. sub setup_yast {
  339.  
  340.     # set up the yast connection if we're running under yast...
  341.     if ($ENV{YAST_IS_RUNNING}) {
  342.  
  343.         # load the yast module if available.
  344.         eval { require ycp; };
  345.         unless ($@) {
  346.             import ycp;
  347.  
  348.             $UI_Mode = "yast";
  349.  
  350.             # let the frontend know that we're starting
  351.             SendDataToYast({
  352.                 type   => "initial_handshake",
  353.                 status => "backend_starting"
  354.             });
  355.  
  356.             # see if the frontend is just starting up also...
  357.             my ($ypath, $yarg) = GetDataFromYast();
  358.             unless ($yarg
  359.                 && (ref($yarg)      eq "HASH")
  360.                 && ($yarg->{type}   eq "initial_handshake")
  361.                 && ($yarg->{status} eq "frontend_starting"))
  362.             {
  363.  
  364.                 # something's broken, die a horrible, painful death
  365.                 fatal_error "Yast frontend is out of sync from backend agent.";
  366.             }
  367.             $DEBUGGING && debug "Initial handshake ok";
  368.  
  369.             # the yast connection seems to be working okay
  370.             return 1;
  371.         }
  372.  
  373.     }
  374.  
  375.     # couldn't init yast
  376.     return 0;
  377. }
  378.  
  379. sub shutdown_yast {
  380.     if ($UI_Mode eq "yast") {
  381.         SendDataToYast({ type => "final_shutdown" });
  382.         my ($ypath, $yarg) = GetDataFromYast();
  383.     }
  384. }
  385.  
  386. sub check_for_subdomain () {
  387.  
  388.     my ($support_subdomainfs, $support_securityfs);
  389.     if (open(MOUNTS, "/proc/filesystems")) {
  390.         while (<MOUNTS>) {
  391.             $support_subdomainfs = 1 if m/subdomainfs/;
  392.             $support_securityfs  = 1 if m/securityfs/;
  393.         }
  394.         close(MOUNTS);
  395.     }
  396.  
  397.     my $sd_mountpoint = "";
  398.     if (open(MOUNTS, "/proc/mounts")) {
  399.         while (<MOUNTS>) {
  400.             if ($support_subdomainfs) {
  401.                 $sd_mountpoint = $1 if m/^\S+\s+(\S+)\s+subdomainfs\s/;
  402.             } elsif ($support_securityfs) {
  403.                 if (m/^\S+\s+(\S+)\s+securityfs\s/) {
  404.                     if (-e "$1/apparmor") {
  405.                         $sd_mountpoint = "$1/apparmor";
  406.                     } elsif (-e "$1/subdomain") {
  407.                         $sd_mountpoint = "$1/subdomain";
  408.                     }
  409.                 }
  410.             }
  411.         }
  412.         close(MOUNTS);
  413.     }
  414.  
  415.     # make sure that subdomain is actually mounted there
  416.     $sd_mountpoint = undef unless -f "$sd_mountpoint/profiles";
  417.  
  418.     return $sd_mountpoint;
  419. }
  420.  
  421. sub which ($) {
  422.     my $file = shift;
  423.  
  424.     foreach my $dir (split(/:/, $ENV{PATH})) {
  425.         return "$dir/$file" if -x "$dir/$file";
  426.     }
  427.  
  428.     return undef;
  429. }
  430.  
  431. # we need to convert subdomain regexps to perl regexps
  432. sub convert_regexp ($) {
  433.     my $regexp = shift;
  434.  
  435.     # escape regexp-special characters we don't support
  436.     $regexp =~ s/(?<!\\)(\.|\+|\$)/\\$1/g;
  437.  
  438.     # * and ** globs can't collapse to match an empty string when they're
  439.     # the only part of the glob at a specific directory level, which
  440.     # complicates things a little.
  441.  
  442.     # ** globs match multiple directory levels
  443.     $regexp =~ s{(?<!\\)\*\*+}{
  444.       my ($pre, $post) = ($`, $');
  445.       if (($pre =~ /\/$/) && (!$post || $post =~ /^\//)) {
  446.         'SD_INTERNAL_MULTI_REQUIRED';
  447.       } else {
  448.         'SD_INTERNAL_MULTI_OPTIONAL';
  449.       }
  450.     }gex;
  451.  
  452.     # convert * globs to match anything at the current path level
  453.     $regexp =~ s{(?<!\\)\*}{
  454.       my ($pre, $post) = ($`, $');
  455.       if (($pre =~ /\/$/) && (!$post || $post =~ /^\//)) {
  456.         'SD_INTERNAL_SINGLE_REQUIRED';
  457.       } else {
  458.         'SD_INTERNAL_SINGLE_OPTIONAL';
  459.       }
  460.     }gex;
  461.  
  462.     # convert ? globs to match a single character at current path level
  463.     $regexp =~ s/(?<!\\)\?/[^\/]/g;
  464.  
  465.     # convert {foo,baz} to (foo|baz)
  466.     $regexp =~ y/\{\}\,/\(\)\|/ if $regexp =~ /\{.*\,.*\}/;
  467.  
  468.     # convert internal markers to their appropriate regexp equivalents
  469.     $regexp =~ s/SD_INTERNAL_SINGLE_OPTIONAL/[^\/]*/g;
  470.     $regexp =~ s/SD_INTERNAL_SINGLE_REQUIRED/[^\/]+/g;
  471.     $regexp =~ s/SD_INTERNAL_MULTI_OPTIONAL/.*/g;
  472.     $regexp =~ s/SD_INTERNAL_MULTI_REQUIRED/[^\/].*/g;
  473.  
  474.     return $regexp;
  475. }
  476.  
  477. sub get_full_path ($) {
  478.     my $originalpath = shift;
  479.  
  480.     my $path = $originalpath;
  481.  
  482.     # keep track so we can break out of loops
  483.     my $linkcount = 0;
  484.  
  485.     # if we don't have any directory foo, look in the current dir
  486.     $path = cwd() . "/$path" if $path !~ m/\//;
  487.  
  488.     # beat symlinks into submission
  489.     while (-l $path) {
  490.  
  491.         if ($linkcount++ > 64) {
  492.             fatal_error "Followed too many symlinks resolving $originalpath";
  493.         }
  494.  
  495.         # split out the directory/file components
  496.         if ($path =~ m/^(.*)\/(.+)$/) {
  497.             my ($dir, $file) = ($1, $2);
  498.  
  499.             # figure out where the link is pointing...
  500.             my $link = readlink($path);
  501.             if ($link =~ /^\//) {
  502.                 # if it's an absolute link, just replace it
  503.                 $path = $link;
  504.             } else {
  505.                 # if it's relative, let abs_path handle it
  506.                 $path = $dir . "/$link";
  507.             }
  508.         }
  509.     }
  510.  
  511.     if (-f $path) {
  512.         my ($dir, $file) = $path =~ m/^(.*)\/(.+)$/;
  513.         $path = realpath($dir) . "/$file";
  514.     } else {
  515.         $path = realpath($path);
  516.     }
  517.  
  518.     return $path;
  519. }
  520.  
  521. sub findexecutable ($) {
  522.     my $bin = shift;
  523.  
  524.     my $fqdbin;
  525.     if (-e $bin) {
  526.         $fqdbin = get_full_path($bin);
  527.         chomp($fqdbin);
  528.     } else {
  529.         if ($bin !~ /\//) {
  530.             my $which = which($bin);
  531.             if ($which) {
  532.                 $fqdbin = get_full_path($which);
  533.             }
  534.         }
  535.     }
  536.  
  537.     unless ($fqdbin && -e $fqdbin) {
  538.         return undef;
  539.     }
  540.  
  541.     return $fqdbin;
  542. }
  543.  
  544. sub name_to_prof_filename($) {
  545.     my $bin    = shift;
  546.     my $filename;
  547.  
  548.     unless ($bin =~ /^($profiledir)/) {
  549.     my $fqdbin = findexecutable($bin);
  550.     if ($fqdbin) {
  551.         $filename = getprofilefilename($fqdbin);
  552.         return ($filename, $fqdbin) if -f $filename;
  553.     }
  554.     }
  555.  
  556.     if ($bin =~ /^$profiledir(.*)/) {
  557.     my $profile = $1;
  558.     return ($bin, $profile);
  559.     } elsif ($bin =~ /^\//) {
  560.     $filename = getprofilefilename($bin);
  561.     return ($filename, $bin);
  562.     } else {
  563.     # not an absolute path try it as a profile_
  564.     $bin = $1 if ($bin !~ /^profile_(.*)/);
  565.     $filename = getprofilefilename($bin);
  566.     return ($filename, "profile_${bin}");
  567.     }
  568.     return undef;
  569. }
  570.  
  571. sub complain ($) {
  572.     my $bin = shift;
  573.  
  574.     return if (!$bin);
  575.  
  576.     my ($filename, $name) = name_to_prof_filename($bin)
  577.     or fatal_error(sprintf(gettext('Can\'t find %s.'), $bin));
  578.  
  579.     UI_Info(sprintf(gettext('Setting %s to complain mode.'), $name));
  580.  
  581.     setprofileflags($filename, "complain");
  582. }
  583.  
  584. sub enforce ($) {
  585.     my $bin = shift;
  586.  
  587.     return if (!$bin);
  588.  
  589.     my ($filename, $name) = name_to_prof_filename($bin)
  590.     or fatal_error(sprintf(gettext('Can\'t find %s.'), $bin));
  591.  
  592.     UI_Info(sprintf(gettext('Setting %s to enforce mode.'), $name));
  593.  
  594.     setprofileflags($filename, "");
  595. }
  596.  
  597. sub head ($) {
  598.     my $file = shift;
  599.  
  600.     my $first = "";
  601.     if (open(FILE, $file)) {
  602.         $first = <FILE>;
  603.         close(FILE);
  604.     }
  605.  
  606.     return $first;
  607. }
  608.  
  609. sub get_output (@) {
  610.     my ($program, @args) = @_;
  611.  
  612.     my $ret = -1;
  613.  
  614.     my $pid;
  615.     my @output;
  616.  
  617.     if (-x $program) {
  618.         $pid = open(KID_TO_READ, "-|");
  619.         unless (defined $pid) {
  620.             fatal_error "can't fork: $!";
  621.         }
  622.  
  623.         if ($pid) {
  624.             while (<KID_TO_READ>) {
  625.                 chomp;
  626.                 push @output, $_;
  627.             }
  628.             close(KID_TO_READ);
  629.             $ret = $?;
  630.         } else {
  631.             ($>, $)) = ($<, $();
  632.             open(STDERR, ">&STDOUT")
  633.               || fatal_error "can't dup stdout to stderr";
  634.             exec($program, @args) || fatal_error "can't exec program: $!";
  635.  
  636.             # NOTREACHED
  637.         }
  638.     }
  639.  
  640.     return ($ret, @output);
  641. }
  642.  
  643. sub get_reqs ($) {
  644.     my $file = shift;
  645.  
  646.     my @reqs;
  647.     my ($ret, @ldd) = get_output($ldd, $file);
  648.  
  649.     if ($ret == 0) {
  650.         for my $line (@ldd) {
  651.             last if $line =~ /not a dynamic executable/;
  652.             last if $line =~ /cannot read header/;
  653.             last if $line =~ /statically linked/;
  654.  
  655.             # avoid new kernel 2.6 poo
  656.             next if $line =~ /linux-(gate|vdso(32|64)).so/;
  657.  
  658.             if ($line =~ /^\s*\S+ => (\/\S+)/) {
  659.                 push @reqs, $1;
  660.             } elsif ($line =~ /^\s*(\/\S+)/) {
  661.                 push @reqs, $1;
  662.             }
  663.         }
  664.     }
  665.  
  666.     return @reqs;
  667. }
  668.  
  669. sub handle_binfmt ($$) {
  670.     my ($profile, $fqdbin) = @_;
  671.  
  672.     my %reqs;
  673.     my @reqs = get_reqs($fqdbin);
  674.  
  675.     while (my $library = shift @reqs) {
  676.  
  677.         $library = get_full_path($library);
  678.  
  679.         push @reqs, get_reqs($library) unless $reqs{$library}++;
  680.  
  681.         # does path match anything pulled in by includes in original profile?
  682.         my $combinedmode = match_prof_incs_to_path($profile, 'allow', $library);
  683.  
  684.         # if we found any matching entries, do the modes match?
  685.         next if $combinedmode;
  686.  
  687.         $library = globcommon($library);
  688.         chomp $library;
  689.         next unless $library;
  690.  
  691.         $profile->{allow}{path}->{$library}{mode} = str_to_mode("mr");
  692.         $profile->{allow}{path}->{$library}{audit} = 0;
  693.     }
  694. }
  695.  
  696. sub get_inactive_profile {
  697.     my $fqdbin = shift;
  698.     if ( $extras{$fqdbin} ) {
  699.         return {$fqdbin => $extras{$fqdbin}};
  700.     }
  701. }
  702.  
  703.  
  704.  
  705. sub create_new_profile {
  706.     my $fqdbin = shift;
  707.  
  708.     my $profile;
  709.     if ($fqdbin =~ /^\// ) {
  710.     $profile = {
  711.         $fqdbin => {
  712.         flags   => "complain",
  713.         include => { "abstractions/base" => 1    },
  714.         path    => { $fqdbin => { mode => str_to_mode("mr") } },
  715.         }
  716.     };
  717.     } else {
  718.     $profile = {
  719.         $fqdbin => {
  720.         flags   => "complain",
  721.         include => { "abstractions/base" => 1    },
  722.         }
  723.     };
  724.     }
  725.  
  726.     # if the executable exists on this system, pull in extra dependencies
  727.     if (-f $fqdbin) {
  728.         my $hashbang = head($fqdbin);
  729.         if ($hashbang && $hashbang =~ /^#!\s*(\S+)/) {
  730.             my $interpreter = get_full_path($1);
  731.             $profile->{$fqdbin}{allow}{path}->{$interpreter}{mode} = str_to_mode("ix");
  732.             $profile->{$fqdbin}{allow}{path}->{$interpreter}{audit} = 0;
  733.             if ($interpreter =~ /perl/) {
  734.                 $profile->{$fqdbin}{include}->{"abstractions/perl"} = 1;
  735.             } elsif ($interpreter =~ m/\/bin\/(bash|sh)/) {
  736.                 $profile->{$fqdbin}{include}->{"abstractions/bash"} = 1;
  737.             }
  738.             handle_binfmt($profile->{$fqdbin}, $interpreter);
  739.         } else {
  740.           handle_binfmt($profile->{$fqdbin}, $fqdbin);
  741.         }
  742.     }
  743.  
  744.     # create required infrastructure hats if it's a known change_hat app
  745.     for my $hatglob (keys %{$cfg->{required_hats}}) {
  746.         if ($fqdbin =~ /$hatglob/) {
  747.             for my $hat (sort split(/\s+/, $cfg->{required_hats}{$hatglob})) {
  748.                 $profile->{$hat} = { flags => "complain" };
  749.             }
  750.         }
  751.     }
  752.     push @created, $fqdbin;
  753.     return { $fqdbin => $profile };
  754. }
  755.  
  756. sub delete_profile ($) {
  757.     my $profile = shift;
  758.     my $profilefile = getprofilefilename( $profile );
  759.     if ( -e $profilefile ) {
  760.       unlink( $profilefile );
  761.     }
  762.     if ( defined $sd{$profile} ) {
  763.         delete $sd{$profile};
  764.     }
  765. }
  766.  
  767. sub get_profile {
  768.     my $fqdbin = shift;
  769.     my $profile_data;
  770.  
  771.     my $distro     = $cfg->{repository}{distro};
  772.     my $repo_url   = $cfg->{repository}{url};
  773.     my @profiles;
  774.     my %profile_hash;
  775.  
  776.     if (repo_is_enabled()) {
  777.        my $results;
  778.        UI_BusyStart( gettext("Connecting to repository.....") );
  779.  
  780.        my ($status_ok,$ret) =
  781.            fetch_profiles_by_name($repo_url, $distro, $fqdbin );
  782.        UI_BusyStop();
  783.        if ( $status_ok ) {
  784.            %profile_hash = %$ret;
  785.        } else {
  786.            my $errmsg =
  787.              sprintf(gettext("WARNING: Error fetching profiles from the repository:\n%s\n"),
  788.                      $ret?$ret:gettext("UNKNOWN ERROR"));
  789.            UI_Important( $errmsg );
  790.        }
  791.     }
  792.  
  793.     my $inactive_profile = get_inactive_profile($fqdbin);
  794.     if ( defined $inactive_profile && $inactive_profile ne "" ) {
  795.         # set the profile to complain mode
  796.         my $uname = gettext( "Inactive local profile for ") . $fqdbin;
  797.         $inactive_profile->{$fqdbin}{$fqdbin}{flags} = "complain";
  798.     # inactive profiles store where they came from
  799.     delete $inactive_profile->{$fqdbin}{$fqdbin}{filename};
  800.         $profile_hash{$uname} =
  801.             {
  802.               "username"     => $uname,
  803.               "profile_type" => "INACTIVE_LOCAL",
  804.               "profile"      => serialize_profile($inactive_profile->{$fqdbin},
  805.                                   $fqdbin
  806.                                 ),
  807.               "profile_data" => $inactive_profile,
  808.             };
  809.     }
  810.  
  811.     return undef if ( keys %profile_hash == 0 ); # No repo profiles, no inactive
  812.                                             # profile
  813.     my @options;
  814.     my @tmp_list;
  815.     my $preferred_present = 0;
  816.     my $preferred_user  = $cfg->{repository}{preferred_user} || "NOVELL";
  817.  
  818.     foreach my $p ( keys %profile_hash ) {
  819.         if ( $profile_hash{$p}->{username} eq $preferred_user ) {
  820.              $preferred_present = 1;
  821.         } else {
  822.             push @tmp_list, $profile_hash{$p}->{username};
  823.         }
  824.     }
  825.  
  826.     if ( $preferred_present ) {
  827.         push  @options, $preferred_user;
  828.     }
  829.     push  @options, @tmp_list;
  830.  
  831.     my $q = {};
  832.     $q->{headers} = [];
  833.     push @{ $q->{headers} }, gettext("Profile"), $fqdbin;
  834.  
  835.     $q->{functions} = [ "CMD_VIEW_PROFILE", "CMD_USE_PROFILE",
  836.                         "CMD_CREATE_PROFILE", "CMD_ABORT", "CMD_FINISHED" ];
  837.  
  838.     $q->{default} = "CMD_VIEW_PROFILE";
  839.  
  840.     $q->{options}  = [@options];
  841.     $q->{selected} = 0;
  842.  
  843.     my ($p, $ans, $arg);
  844.     do {
  845.         ($ans, $arg) = UI_PromptUser($q);
  846.         $p = $profile_hash{$options[$arg]};
  847.         for (my $i = 0; $i < scalar(@options); $i++) {
  848.             if ($options[$i] eq $options[$arg]) {
  849.                 $q->{selected} = $i;
  850.             }
  851.         }
  852.  
  853.         if ($ans eq "CMD_VIEW_PROFILE") {
  854.             if ($UI_Mode eq "yast") {
  855.                 SendDataToYast(
  856.                     {
  857.                         type         => "dialog-view-profile",
  858.                         user         => $options[$arg],
  859.                         profile      => $p->{profile},
  860.                         profile_type => $p->{profile_type}
  861.                     }
  862.                 );
  863.                 my ($ypath, $yarg) = GetDataFromYast();
  864.             } else {
  865.                 my $pager = get_pager();
  866.                 open(PAGER, "| $pager");
  867.                 print PAGER gettext("Profile submitted by") .
  868.                                     " $options[$arg]:\n\n" . $p->{profile} . "\n\n";
  869.                 close(PAGER);
  870.             }
  871.         } elsif ($ans eq "CMD_USE_PROFILE") {
  872.             if ( $p->{profile_type} eq "INACTIVE_LOCAL" ) {
  873.                 $profile_data = $p->{profile_data};
  874.                 push @created, $fqdbin; # This really is ugly here
  875.                                         # need to find a better place to mark
  876.                                         # this as newly created
  877.             } else {
  878.                 $profile_data =
  879.                     parse_repo_profile($fqdbin, $repo_url, $p);
  880.             }
  881.         }
  882.     } until ($ans =~ /^CMD_(USE_PROFILE|CREATE_PROFILE)$/);
  883.  
  884.     return $profile_data;
  885. }
  886.  
  887. sub activate_repo_profiles ($$$) {
  888.     my ($url,$profiles,$complain) = @_;
  889.  
  890.     readprofiles();
  891.     eval {
  892.         for my $p ( @$profiles ) {
  893.             my $pname = $p->[0];
  894.             my $profile_data = parse_repo_profile( $pname, $url, $p->[1] );
  895.             attach_profile_data(\%sd, $profile_data);
  896.             writeprofile($pname);
  897.             if ( $complain ) {
  898.                 my $filename = getprofilefilename($pname);
  899.                 setprofileflags($filename, "complain");
  900.                 UI_Info(sprintf(gettext('Setting %s to complain mode.'),
  901.                                         $pname));
  902.             }
  903.         }
  904.     };
  905.     # if there were errors....
  906.     if ($@) {
  907.         $@ =~ s/\n$//;
  908.         print STDERR sprintf(gettext("Error activating profiles: %s\n"), $@);
  909.     }
  910. }
  911.  
  912. sub autodep_base($$) {
  913.     my ($bin, $pname) = @_;
  914.     %extras = ();
  915.  
  916.     $bin = $pname if (! $bin) && ($pname =~ /^\//);
  917.  
  918.     unless ($repo_cfg || not defined $cfg->{repository}{url}) {
  919.         $repo_cfg = read_config("repository.conf");
  920.         if ( (not defined $repo_cfg->{repository}) ||
  921.              ($repo_cfg->{repository}{enabled} eq "later") ) {
  922.                 UI_ask_to_enable_repo();
  923.         }
  924.     }
  925.  
  926.     my $fqdbin;
  927.     if ($bin) {
  928.     # findexecutable() might fail if we're running on a different system
  929.     # than the logs were collected on.  ugly.  we'll just hope for the best.
  930.     $fqdbin = findexecutable($bin) || $bin;
  931.  
  932.     # try to make sure we have a full path in case findexecutable failed
  933.     return unless $fqdbin =~ /^\//;
  934.  
  935.     # ignore directories
  936.     return if -d $fqdbin;
  937.     }
  938.  
  939.     $pname = $fqdbin if $fqdbin;
  940.  
  941.     my $profile_data;
  942.  
  943.     readinactiveprofiles(); # need to read the profiles to see if an
  944.                             # inactive local profile is present
  945.     $profile_data = eval { get_profile($pname) };
  946.  
  947.     unless ($profile_data) {
  948.         $profile_data = create_new_profile($pname);
  949.     }
  950.  
  951.     my $file = getprofilefilename($pname);
  952.  
  953.     # stick the profile into our data structure.
  954.     attach_profile_data(\%sd, $profile_data);
  955.     # and store a "clean" version also so we can display the changes we've
  956.     # made during this run
  957.     attach_profile_data(\%original_sd, $profile_data);
  958.  
  959.     if (-f "$profiledir/tunables/global") {
  960.         unless (exists $filelist{$file}) {
  961.             $filelist{$file} = { };
  962.         }
  963.         $filelist{$file}{include}{'tunables/global'} = 1; # sorry
  964.     }
  965.  
  966.     # write out the profile...
  967.     writeprofile_ui_feedback($pname);
  968. }
  969.  
  970. sub autodep ($) {
  971.     my $bin = shift;
  972.     return autodep_base($bin, "");
  973. }
  974.  
  975. sub getprofilefilename ($) {
  976.     my $profile = shift;
  977.  
  978.     my $filename = $profile;
  979.     if ($filename =~ /^\//) {
  980.     $filename =~ s/^\///;                              # strip leading /
  981.     } else {
  982.     $filename = "profile_$filename";
  983.     }
  984.     $filename =~ s/\//./g;                            # convert /'s to .'s
  985.  
  986.     return "$profiledir/$filename";
  987. }
  988.  
  989. sub setprofileflags ($$) {
  990.     my $filename = shift;
  991.     my $newflags = shift;
  992.  
  993.     if (open(PROFILE, "$filename")) {
  994.         if (open(NEWPROFILE, ">$filename.new")) {
  995.             while (<PROFILE>) {
  996.                 if (m/^\s*("??\/.+?"??)\s+(flags=\(.+\)\s+)*\{\s*$/) {
  997.                     my ($binary, $flags) = ($1, $2);
  998.  
  999.                     if ($newflags) {
  1000.                         $_ = "$binary flags=($newflags) {\n";
  1001.                     } else {
  1002.                         $_ = "$binary {\n";
  1003.                     }
  1004.                 } elsif (m/^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*$/) {
  1005.                     my ($hat, $flags) = ($1, $2);
  1006.  
  1007.                     if ($newflags) {
  1008.                         $_ = "$hat flags=($newflags) {\n";
  1009.                     } else {
  1010.                         $_ = "$hat {\n";
  1011.                     }
  1012.                 }
  1013.                 print NEWPROFILE;
  1014.             }
  1015.             close(NEWPROFILE);
  1016.             rename("$filename.new", "$filename");
  1017.         }
  1018.         close(PROFILE);
  1019.     }
  1020. }
  1021.  
  1022. sub profile_exists($) {
  1023.     my $program = shift || return 0;
  1024.  
  1025.     # if it's already in the cache, return true
  1026.     return 1 if $existing_profiles{$program};
  1027.  
  1028.     # if the profile exists, mark it in the cache and return true
  1029.     my $profile = getprofilefilename($program);
  1030.     if (-e $profile) {
  1031.         $existing_profiles{$program} = 1;
  1032.         return 1;
  1033.     }
  1034.  
  1035.     # couldn't find a profile, so we'll return false
  1036.     return 0;
  1037. }
  1038.  
  1039. sub sync_profiles {
  1040.  
  1041.     my ($user, $pass) = get_repo_user_pass();
  1042.     return unless ( $user && $pass );
  1043.  
  1044.     my @repo_profiles;
  1045.     my @changed_profiles;
  1046.     my @new_profiles;
  1047.     my $serialize_opts = { };
  1048.     my ($status_ok,$ret) =
  1049.         fetch_profiles_by_user($cfg->{repository}{url},
  1050.                                $cfg->{repository}{distro},
  1051.                                $user
  1052.                               );
  1053.     if ( !$status_ok ) {
  1054.         my $errmsg =
  1055.           sprintf(gettext("WARNING: Error syncronizing profiles with the repository:\n%s\n"),
  1056.                   $ret?$ret:gettext("UNKNOWN ERROR"));
  1057.         UI_Important($errmsg);
  1058.         return;
  1059.     } else {
  1060.         my $users_repo_profiles = $ret;
  1061.         $serialize_opts->{NO_FLAGS} = 1;
  1062.         #
  1063.         # Find changes made to non-repo profiles
  1064.         #
  1065.         for my $profile (sort keys %sd) {
  1066.             if (is_repo_profile($sd{$profile}{$profile})) {
  1067.                 push @repo_profiles, $profile;
  1068.             }
  1069.             if ( grep(/^$profile$/, @created) )  {
  1070.                 my $p_local = serialize_profile($sd{$profile},
  1071.                                                 $profile,
  1072.                                                 $serialize_opts);
  1073.                 if ( not defined $users_repo_profiles->{$profile} ) {
  1074.                     push @new_profiles,  [ $profile, $p_local, "" ];
  1075.                 } else {
  1076.                     my $p_repo = $users_repo_profiles->{$profile}->{profile};
  1077.                     if ( $p_local ne $p_repo ) {
  1078.                         push @changed_profiles, [ $profile, $p_local, $p_repo ];
  1079.                     }
  1080.                 }
  1081.             }
  1082.         }
  1083.  
  1084.         #
  1085.         # Find changes made to local profiles with repo metadata
  1086.         #
  1087.         if (@repo_profiles) {
  1088.             for my $profile (@repo_profiles) {
  1089.                 my $p_local = serialize_profile($sd{$profile},
  1090.                                                 $profile,
  1091.                                                 $serialize_opts);
  1092.                 if ( not exists $users_repo_profiles->{$profile} ) {
  1093.                     push @new_profiles,  [ $profile, $p_local, "" ];
  1094.                 } else {
  1095.                     my $p_repo = "";
  1096.                     if ( $sd{$profile}{$profile}{repo}{user} eq $user ) {
  1097.                        $p_repo = $users_repo_profiles->{$profile}->{profile};
  1098.                     }  else {
  1099.                         my ($status_ok,$ret) =
  1100.                             fetch_profile_by_id($cfg->{repository}{url},
  1101.                                                 $sd{$profile}{$profile}{repo}{id}
  1102.                                                );
  1103.                         if ( $status_ok ) {
  1104.                            $p_repo = $ret->{profile};
  1105.                         } else {
  1106.                             my $errmsg =
  1107.                               sprintf(
  1108.                                 gettext("WARNING: Error syncronizing profiles with the repository:\n%s\n"),
  1109.                                 $ret?$ret:gettext("UNKNOWN ERROR"));
  1110.                             UI_Important($errmsg);
  1111.                             next;
  1112.                         }
  1113.                     }
  1114.                     if ( $p_repo ne $p_local ) {
  1115.                         push @changed_profiles, [ $profile, $p_local, $p_repo ];
  1116.                     }
  1117.                 }
  1118.             }
  1119.         }
  1120.  
  1121.         if ( @changed_profiles ) {
  1122.            submit_changed_profiles( \@changed_profiles );
  1123.         }
  1124.         if ( @new_profiles ) {
  1125.            submit_created_profiles( \@new_profiles );
  1126.         }
  1127.     }
  1128. }
  1129.  
  1130. sub submit_created_profiles {
  1131.     my $new_profiles = shift;
  1132.     my $url = $cfg->{repository}{url};
  1133.  
  1134.     if ($UI_Mode eq "yast") {
  1135.         my $title       = gettext("New profiles");
  1136.         my $explanation =
  1137.           gettext("Please choose the newly created profiles that you would".
  1138.           " like\nto store in the repository");
  1139.         yast_select_and_upload_profiles($title,
  1140.                                         $explanation,
  1141.                                         $new_profiles);
  1142.     } else {
  1143.         my $title       =
  1144.           gettext("Submit newly created profiles to the repository");
  1145.         my $explanation =
  1146.           gettext("Would you like to upload the newly created profiles?");
  1147.         console_select_and_upload_profiles($title,
  1148.                                            $explanation,
  1149.                                            $new_profiles);
  1150.     }
  1151. }
  1152.  
  1153. sub submit_changed_profiles {
  1154.     my $changed_profiles = shift;
  1155.     my $url = $cfg->{repository}{url};
  1156.     if (@$changed_profiles) {
  1157.         if ($UI_Mode eq "yast") {
  1158.             my $explanation =
  1159.               gettext("Select which of the changed profiles you would".
  1160.               " like to upload\nto the repository");
  1161.             my $title       = gettext("Changed profiles");
  1162.             yast_select_and_upload_profiles($title,
  1163.                                             $explanation,
  1164.                                             $changed_profiles);
  1165.         } else {
  1166.             my $title       =
  1167.               gettext("Submit changed profiles to the repository");
  1168.             my $explanation =
  1169.               gettext("The following profiles from the repository were".
  1170.               " changed.\nWould you like to upload your changes?");
  1171.             console_select_and_upload_profiles($title,
  1172.                                                $explanation,
  1173.                                                $changed_profiles);
  1174.         }
  1175.     }
  1176. }
  1177.  
  1178. sub yast_select_and_upload_profiles {
  1179.  
  1180.     my ($title, $explanation, $profiles_ref) = @_;
  1181.     my $url = $cfg->{repository}{url};
  1182.     my %profile_changes;
  1183.     my @profiles = @$profiles_ref;
  1184.  
  1185.     foreach my $prof (@profiles) {
  1186.         $profile_changes{ $prof->[0] } =
  1187.           get_profile_diff($prof->[2], $prof->[1]);
  1188.     }
  1189.  
  1190.     my (@selected_profiles, $changelog, $changelogs, $single_changelog);
  1191.     SendDataToYast(
  1192.         {
  1193.             type               => "dialog-select-profiles",
  1194.             title              => $title,
  1195.             explanation        => $explanation,
  1196.             default_select     => "false",
  1197.             disable_ask_upload => "true",
  1198.             profiles           => \%profile_changes
  1199.         }
  1200.     );
  1201.     my ($ypath, $yarg) = GetDataFromYast();
  1202.     if ($yarg->{STATUS} eq "cancel") {
  1203.         return;
  1204.     } else {
  1205.         my $selected_profiles_ref = $yarg->{PROFILES};
  1206.         @selected_profiles = @$selected_profiles_ref;
  1207.         $changelogs        = $yarg->{CHANGELOG};
  1208.         if (defined $changelogs->{SINGLE_CHANGELOG}) {
  1209.             $changelog        = $changelogs->{SINGLE_CHANGELOG};
  1210.             $single_changelog = 1;
  1211.         }
  1212.     }
  1213.  
  1214.     for my $profile (@selected_profiles) {
  1215.         my ($user, $pass) = get_repo_user_pass();
  1216.         my $profile_string = serialize_profile($sd{$profile}, $profile);
  1217.         if (!$single_changelog) {
  1218.             $changelog = $changelogs->{$profile};
  1219.         }
  1220.         my ($status_ok, $ret) = upload_profile( $url,
  1221.                                                 $user,
  1222.                                                 $pass,
  1223.                                                 $cfg->{repository}{distro},
  1224.                                                 $profile,
  1225.                                                 $profile_string,
  1226.                                                 $changelog
  1227.                                               );
  1228.         if ($status_ok) {
  1229.             my $newprofile = $ret;
  1230.             my $newid      = $newprofile->{id};
  1231.             set_repo_info($sd{$profile}{$profile}, $url, $user, $newid);
  1232.             writeprofile_ui_feedback($profile);
  1233.         } else {
  1234.             my $errmsg =
  1235.               sprintf(
  1236.                 gettext("WARNING: An error occured while uploading the profile %s\n%s\n"),
  1237.                 $profile, $ret?$ret:gettext("UNKNOWN ERROR"));
  1238.             UI_Important( $errmsg );
  1239.         }
  1240.     }
  1241.     UI_Info(gettext("Uploaded changes to repository."));
  1242.  
  1243.     # Check to see if unselected profiles should be marked as local only
  1244.     # this is outside of the main repo code as we want users to be able to mark
  1245.     # profiles as local only even if they aren't able to connect to the repo.
  1246.     if (defined $yarg->{NEVER_ASK_AGAIN}) {
  1247.         my @unselected_profiles;
  1248.         foreach my $prof (@profiles) {
  1249.             if ( grep(/^$prof->[0]$/, @selected_profiles) == 0 ) {
  1250.                 push @unselected_profiles, $prof->[0];
  1251.             }
  1252.         }
  1253.         set_profiles_local_only( @unselected_profiles );
  1254.     }
  1255. }
  1256.  
  1257. sub console_select_and_upload_profiles {
  1258.     my ($title, $explanation, $profiles_ref) = @_;
  1259.     my $url = $cfg->{repository}{url};
  1260.     my @profiles = @$profiles_ref;
  1261.     my $q = {};
  1262.     $q->{title} = $title;
  1263.     $q->{headers} = [ "Repository", $url, ];
  1264.  
  1265.     $q->{explanation} = $explanation;
  1266.  
  1267.     $q->{functions} = [ "CMD_UPLOAD_CHANGES",
  1268.                         "CMD_VIEW_CHANGES",
  1269.                         "CMD_ASK_LATER",
  1270.                         "CMD_ASK_NEVER",
  1271.                         "CMD_ABORT", ];
  1272.  
  1273.     $q->{default} = "CMD_VIEW_CHANGES";
  1274.  
  1275.     $q->{options} = [ map { $_->[0] } @profiles ];
  1276.     $q->{selected} = 0;
  1277.  
  1278.     my ($ans, $arg);
  1279.     do {
  1280.         ($ans, $arg) = UI_PromptUser($q);
  1281.  
  1282.         if ($ans eq "CMD_VIEW_CHANGES") {
  1283.             display_changes($profiles[$arg]->[2], $profiles[$arg]->[1]);
  1284.         }
  1285.     } until $ans =~ /^CMD_(UPLOAD_CHANGES|ASK_NEVER|ASK_LATER)/;
  1286.  
  1287.     if ($ans eq "CMD_ASK_NEVER") {
  1288.         set_profiles_local_only(  map { $_->[0] } @profiles  );
  1289.     } elsif ($ans eq "CMD_UPLOAD_CHANGES") {
  1290.         my $changelog = UI_GetString(gettext("Changelog Entry: "), "");
  1291.         my ($user, $pass) = get_repo_user_pass();
  1292.         if ($user && $pass) {
  1293.             for my $p_data (@profiles) {
  1294.                 my $profile          = $p_data->[0];
  1295.                 my $profile_string   = $p_data->[1];
  1296.                 my ($status_ok,$ret) =
  1297.                     upload_profile( $url,
  1298.                                     $user,
  1299.                                     $pass,
  1300.                                     $cfg->{repository}{distro},
  1301.                                     $profile,
  1302.                                     $profile_string,
  1303.                                     $changelog
  1304.                                   );
  1305.                 if ($status_ok) {
  1306.                     my $newprofile = $ret;
  1307.                     my $newid      = $newprofile->{id};
  1308.                     set_repo_info($sd{$profile}{$profile}, $url, $user, $newid);
  1309.                     writeprofile_ui_feedback($profile);
  1310.                     UI_Info(
  1311.                       sprintf(gettext("Uploaded %s to repository."), $profile)
  1312.                     );
  1313.                 } else {
  1314.                     my $errmsg =
  1315.                       sprintf(
  1316.                         gettext("WARNING: An error occured while uploading the profile %s\n%s\n"),
  1317.                         $profile, $ret?$ret:gettext("UNKNOWN ERROR"));
  1318.                     UI_Important( $errmsg );
  1319.                 }
  1320.             }
  1321.         } else {
  1322.             UI_Important(gettext("Repository Error\n" .
  1323.                       "Registration or Signin was unsuccessful. User login\n" .
  1324.                       "information is required to upload profiles to the\n" .
  1325.                       "repository. These changes have not been sent.\n"));
  1326.         }
  1327.     }
  1328. }
  1329.  
  1330. #
  1331. # Mark the profiles passed in @profiles as local only
  1332. # and don't prompt to upload changes to the repository
  1333. #
  1334. sub set_profiles_local_only {
  1335.     my @profiles = @_;
  1336.     for my $profile (@profiles) {
  1337.          $sd{$profile}{$profile}{repo}{neversubmit} = 1;
  1338.          writeprofile_ui_feedback($profile);
  1339.     }
  1340. }
  1341.  
  1342. ##########################################################################
  1343. # Here are the console/yast interface functions
  1344.  
  1345. sub UI_Info ($) {
  1346.     my $text = shift;
  1347.  
  1348.     $DEBUGGING && debug "UI_Info: $UI_Mode: $text";
  1349.  
  1350.     if ($UI_Mode eq "text") {
  1351.         print "$text\n";
  1352.     } else {
  1353.         ycp::y2milestone($text);
  1354.     }
  1355. }
  1356.  
  1357. sub UI_Important ($) {
  1358.     my $text = shift;
  1359.  
  1360.     $DEBUGGING && debug "UI_Important: $UI_Mode: $text";
  1361.  
  1362.     if ($UI_Mode eq "text") {
  1363.         print "\n$text\n";
  1364.     } else {
  1365.         SendDataToYast({ type => "dialog-error", message => $text });
  1366.         my ($path, $yarg) = GetDataFromYast();
  1367.     }
  1368. }
  1369.  
  1370. sub UI_YesNo ($$) {
  1371.     my $text    = shift;
  1372.     my $default = shift;
  1373.  
  1374.     $DEBUGGING && debug "UI_YesNo: $UI_Mode: $text $default";
  1375.  
  1376.     my $ans;
  1377.     if ($UI_Mode eq "text") {
  1378.  
  1379.         my $yes = gettext("(Y)es");
  1380.         my $no  = gettext("(N)o");
  1381.  
  1382.         # figure out our localized hotkeys
  1383.         my $usrmsg = "PromptUser: " . gettext("Invalid hotkey for");
  1384.         $yes =~ /\((\S)\)/ or fatal_error "$usrmsg '$yes'";
  1385.         my $yeskey = lc($1);
  1386.         $no =~ /\((\S)\)/ or fatal_error "$usrmsg '$no'";
  1387.         my $nokey = lc($1);
  1388.  
  1389.         print "\n$text\n";
  1390.         if ($default eq "y") {
  1391.             print "\n[$yes] / $no\n";
  1392.         } else {
  1393.             print "\n$yes / [$no]\n";
  1394.         }
  1395.         $ans = getkey() || (($default eq "y") ? $yeskey : $nokey);
  1396.  
  1397.         # convert back from a localized answer to english y or n
  1398.         $ans = (lc($ans) eq $yeskey) ? "y" : "n";
  1399.     } else {
  1400.  
  1401.         SendDataToYast({ type => "dialog-yesno", question => $text });
  1402.         my ($ypath, $yarg) = GetDataFromYast();
  1403.         $ans = $yarg->{answer} || $default;
  1404.  
  1405.     }
  1406.  
  1407.     return $ans;
  1408. }
  1409.  
  1410. sub UI_YesNoCancel ($$) {
  1411.     my $text    = shift;
  1412.     my $default = shift;
  1413.  
  1414.     $DEBUGGING && debug "UI_YesNoCancel: $UI_Mode: $text $default";
  1415.  
  1416.     my $ans;
  1417.     if ($UI_Mode eq "text") {
  1418.  
  1419.         my $yes    = gettext("(Y)es");
  1420.         my $no     = gettext("(N)o");
  1421.         my $cancel = gettext("(C)ancel");
  1422.  
  1423.         # figure out our localized hotkeys
  1424.         my $usrmsg = "PromptUser: " . gettext("Invalid hotkey for");
  1425.         $yes =~ /\((\S)\)/ or fatal_error "$usrmsg '$yes'";
  1426.         my $yeskey = lc($1);
  1427.         $no =~ /\((\S)\)/ or fatal_error "$usrmsg '$no'";
  1428.         my $nokey = lc($1);
  1429.         $cancel =~ /\((\S)\)/ or fatal_error "$usrmsg '$cancel'";
  1430.         my $cancelkey = lc($1);
  1431.  
  1432.         $ans = "XXXINVALIDXXX";
  1433.         while ($ans !~ /^(y|n|c)$/) {
  1434.             print "\n$text\n";
  1435.             if ($default eq "y") {
  1436.                 print "\n[$yes] / $no / $cancel\n";
  1437.             } elsif ($default eq "n") {
  1438.                 print "\n$yes / [$no] / $cancel\n";
  1439.             } else {
  1440.                 print "\n$yes / $no / [$cancel]\n";
  1441.             }
  1442.  
  1443.             $ans = getkey();
  1444.  
  1445.             if ($ans) {
  1446.                 # convert back from a localized answer to english y or n
  1447.                 $ans = lc($ans);
  1448.                 if ($ans eq $yeskey) {
  1449.                     $ans = "y";
  1450.                 } elsif ($ans eq $nokey) {
  1451.                     $ans = "n";
  1452.                 } elsif ($ans eq $cancelkey) {
  1453.                     $ans = "c";
  1454.                 }
  1455.             } else {
  1456.                 $ans = $default;
  1457.             }
  1458.         }
  1459.     } else {
  1460.  
  1461.         SendDataToYast({ type => "dialog-yesnocancel", question => $text });
  1462.         my ($ypath, $yarg) = GetDataFromYast();
  1463.         $ans = $yarg->{answer} || $default;
  1464.  
  1465.     }
  1466.  
  1467.     return $ans;
  1468. }
  1469.  
  1470. sub UI_GetString ($$) {
  1471.     my $text    = shift;
  1472.     my $default = shift;
  1473.  
  1474.     $DEBUGGING && debug "UI_GetString: $UI_Mode: $text $default";
  1475.  
  1476.     my $string;
  1477.     if ($UI_Mode eq "text") {
  1478.  
  1479.         if ($term) {
  1480.             $string = $term->readline($text, $default);
  1481.         } else {
  1482.             local $| = 1;
  1483.             print "$text";
  1484.             $string = <STDIN>;
  1485.             chomp($string);
  1486.         }
  1487.  
  1488.     } else {
  1489.  
  1490.         SendDataToYast({
  1491.             type    => "dialog-getstring",
  1492.             label   => $text,
  1493.             default => $default
  1494.         });
  1495.         my ($ypath, $yarg) = GetDataFromYast();
  1496.         $string = $yarg->{string};
  1497.  
  1498.     }
  1499.     return $string;
  1500. }
  1501.  
  1502. sub UI_GetFile ($) {
  1503.     my $f = shift;
  1504.  
  1505.     $DEBUGGING && debug "UI_GetFile: $UI_Mode";
  1506.  
  1507.     my $filename;
  1508.     if ($UI_Mode eq "text") {
  1509.  
  1510.         local $| = 1;
  1511.         print "$f->{description}\n";
  1512.         $filename = <STDIN>;
  1513.         chomp($filename);
  1514.  
  1515.     } else {
  1516.  
  1517.         $f->{type} = "dialog-getfile";
  1518.  
  1519.         SendDataToYast($f);
  1520.         my ($ypath, $yarg) = GetDataFromYast();
  1521.         if ($yarg->{answer} eq "okay") {
  1522.             $filename = $yarg->{filename};
  1523.         }
  1524.     }
  1525.  
  1526.     return $filename;
  1527. }
  1528.  
  1529. sub UI_BusyStart ($) {
  1530.     my $message = shift;
  1531.     $DEBUGGING && debug "UI_BusyStart: $UI_Mode";
  1532.  
  1533.     if ($UI_Mode eq "text") {
  1534.       UI_Info( $message );
  1535.     } else {
  1536.         SendDataToYast({
  1537.                         type    => "dialog-busy-start",
  1538.                         message => $message,
  1539.                        });
  1540.         my ($ypath, $yarg) = GetDataFromYast();
  1541.     }
  1542. }
  1543.  
  1544. sub UI_BusyStop  {
  1545.     $DEBUGGING && debug "UI_BusyStop: $UI_Mode";
  1546.  
  1547.     if ($UI_Mode ne "text") {
  1548.         SendDataToYast({ type    => "dialog-busy-stop" });
  1549.         my ($ypath, $yarg) = GetDataFromYast();
  1550.     }
  1551. }
  1552.  
  1553.  
  1554. my %CMDS = (
  1555.     CMD_ALLOW            => "(A)llow",
  1556.     CMD_OTHER         => "(M)ore",
  1557.     CMD_AUDIT_NEW     => "Audi(t)",
  1558.     CMD_AUDIT_OFF     => "Audi(t) off",
  1559.     CMD_AUDIT_FULL     => "Audit (A)ll",
  1560.     CMD_OTHER         => "(O)pts",
  1561.     CMD_USER_ON         => "(O)wner permissions on",
  1562.     CMD_USER_OFF     => "(O)wner permissions off",
  1563.     CMD_DENY             => "(D)eny",
  1564.     CMD_ABORT            => "Abo(r)t",
  1565.     CMD_FINISHED         => "(F)inish",
  1566.     CMD_ix               => "(I)nherit",
  1567.     CMD_px               => "(P)rofile",
  1568.     CMD_px_safe         => "(P)rofile Clean Exec",
  1569.     CMD_cx         => "(C)hild",
  1570.     CMD_cx_safe         => "(C)hild Clean Exec",
  1571.     CMD_nx         => "(N)ame",
  1572.     CMD_nx_safe         => "(N)amed Clean Exec",
  1573.     CMD_ux               => "(U)nconfined",
  1574.     CMD_ux_safe         => "(U)nconfined Clean Exec",
  1575.     CMD_pix         => "(P)rofile ix",
  1576.     CMD_pix_safe     => "(P)rofile ix Clean Exec",
  1577.     CMD_cix         => "(C)hild ix",
  1578.     CMD_cix_safe     => "(C)hild ix Cx Clean Exec",
  1579.     CMD_nix         => "(N)ame ix",
  1580.     CMD_nix_safe     => "(N)ame ix",
  1581.     CMD_EXEC_IX_ON     => "(X)ix",
  1582.     CMD_EXEC_IX_OFF     => "(X)ix",
  1583.     CMD_SAVE             => "(S)ave Changes",
  1584.     CMD_CONTINUE         => "(C)ontinue Profiling",
  1585.     CMD_NEW              => "(N)ew",
  1586.     CMD_GLOB             => "(G)lob",
  1587.     CMD_GLOBEXT          => "Glob w/(E)xt",
  1588.     CMD_ADDHAT           => "(A)dd Requested Hat",
  1589.     CMD_USEDEFAULT       => "(U)se Default Hat",
  1590.     CMD_SCAN             => "(S)can system log for SubDomain events",
  1591.     CMD_HELP             => "(H)elp",
  1592.     CMD_VIEW_PROFILE     => "(V)iew Profile",
  1593.     CMD_USE_PROFILE      => "(U)se Profile",
  1594.     CMD_CREATE_PROFILE   => "(C)reate New Profile",
  1595.     CMD_UPDATE_PROFILE   => "(U)pdate Profile",
  1596.     CMD_IGNORE_UPDATE    => "(I)gnore Update",
  1597.     CMD_SAVE_CHANGES     => "(S)ave Changes",
  1598.     CMD_UPLOAD_CHANGES   => "(U)pload Changes",
  1599.     CMD_VIEW_CHANGES     => "(V)iew Changes",
  1600.     CMD_VIEW             => "(V)iew",
  1601.     CMD_ENABLE_REPO      => "(E)nable Repository",
  1602.     CMD_DISABLE_REPO     => "(D)isable Repository",
  1603.     CMD_ASK_NEVER        => "(N)ever Ask Again",
  1604.     CMD_ASK_LATER        => "Ask Me (L)ater",
  1605.     CMD_YES              => "(Y)es",
  1606.     CMD_NO               => "(N)o",
  1607.     CMD_ALL_NET          => "Allow All (N)etwork",
  1608.     CMD_NET_FAMILY       => "Allow Network Fa(m)ily",
  1609.     CMD_OVERWRITE        => "(O)verwrite Profile",
  1610.     CMD_KEEP             => "(K)eep Profile",
  1611.     CMD_CONTINUE         => "(C)ontinue",
  1612. );
  1613.  
  1614. sub UI_PromptUser ($) {
  1615.     my $q = shift;
  1616.  
  1617.     my ($cmd, $arg);
  1618.     if ($UI_Mode eq "text") {
  1619.  
  1620.         ($cmd, $arg) = Text_PromptUser($q);
  1621.  
  1622.     } else {
  1623.  
  1624.         $q->{type} = "wizard";
  1625.  
  1626.         SendDataToYast($q);
  1627.         my ($ypath, $yarg) = GetDataFromYast();
  1628.  
  1629.         $cmd = $yarg->{selection} || "CMD_ABORT";
  1630.         $arg = $yarg->{selected};
  1631.     }
  1632.  
  1633.     if ($cmd eq "CMD_ABORT") {
  1634.         confirm_and_abort();
  1635.         $cmd = "XXXINVALIDXXX";
  1636.     } elsif ($cmd eq "CMD_FINISHED") {
  1637.         confirm_and_finish();
  1638.         $cmd = "XXXINVALIDXXX";
  1639.     }
  1640.  
  1641.     if (wantarray) {
  1642.         return ($cmd, $arg);
  1643.     } else {
  1644.         return $cmd;
  1645.     }
  1646. }
  1647.  
  1648.  
  1649. sub UI_ShortMessage {
  1650.     my ($headline, $message) = @_;
  1651.  
  1652.     SendDataToYast(
  1653.         {
  1654.             type     => "short-dialog-message",
  1655.             headline => $headline,
  1656.             message  => $message
  1657.         }
  1658.     );
  1659.     my ($ypath, $yarg) = GetDataFromYast();
  1660. }
  1661.  
  1662. sub UI_LongMessage {
  1663.     my ($headline, $message) = @_;
  1664.  
  1665.     $headline = "MISSING" if not defined $headline;
  1666.     $message  = "MISSING" if not defined $message;
  1667.  
  1668.     SendDataToYast(
  1669.         {
  1670.             type     => "long-dialog-message",
  1671.             headline => $headline,
  1672.             message  => $message
  1673.         }
  1674.     );
  1675.     my ($ypath, $yarg) = GetDataFromYast();
  1676. }
  1677.  
  1678. ##########################################################################
  1679. # here are the interface functions to send data back and forth between
  1680. # the yast frontend and the perl backend
  1681.  
  1682. # this is super ugly, but waits for the next ycp Read command and sends data
  1683. # back to the ycp front end.
  1684.  
  1685. sub SendDataToYast {
  1686.     my $data = shift;
  1687.  
  1688.     $DEBUGGING && debug "SendDataToYast: Waiting for YCP command";
  1689.  
  1690.     while (<STDIN>) {
  1691.         $DEBUGGING && debug "SendDataToYast: YCP: $_";
  1692.         my ($ycommand, $ypath, $yargument) = ycp::ParseCommand($_);
  1693.  
  1694.         if ($ycommand && $ycommand eq "Read") {
  1695.  
  1696.             if ($DEBUGGING) {
  1697.                 my $debugmsg = Data::Dumper->Dump([$data], [qw(*data)]);
  1698.                 debug "SendDataToYast: Sending--\n$debugmsg";
  1699.             }
  1700.  
  1701.             ycp::Return($data);
  1702.             return 1;
  1703.  
  1704.         } else {
  1705.  
  1706.             $DEBUGGING && debug "SendDataToYast: Expected 'Read' but got-- $_";
  1707.  
  1708.         }
  1709.     }
  1710.  
  1711.     # if we ever break out here, something's horribly wrong.
  1712.     fatal_error "SendDataToYast: didn't receive YCP command before connection died";
  1713. }
  1714.  
  1715. # this is super ugly, but waits for the next ycp Write command and grabs
  1716. # whatever the ycp front end gives us
  1717.  
  1718. sub GetDataFromYast {
  1719.  
  1720.     $DEBUGGING && debug "GetDataFromYast: Waiting for YCP command";
  1721.  
  1722.     while (<STDIN>) {
  1723.         $DEBUGGING && debug "GetDataFromYast: YCP: $_";
  1724.         my ($ycmd, $ypath, $yarg) = ycp::ParseCommand($_);
  1725.  
  1726.         if ($DEBUGGING) {
  1727.             my $debugmsg = Data::Dumper->Dump([$yarg], [qw(*data)]);
  1728.             debug "GetDataFromYast: Received--\n$debugmsg";
  1729.         }
  1730.  
  1731.         if ($ycmd && $ycmd eq "Write") {
  1732.  
  1733.             ycp::Return("true");
  1734.             return ($ypath, $yarg);
  1735.  
  1736.         } else {
  1737.             $DEBUGGING && debug "GetDataFromYast: Expected 'Write' but got-- $_";
  1738.         }
  1739.     }
  1740.  
  1741.     # if we ever break out here, something's horribly wrong.
  1742.     fatal_error "GetDataFromYast: didn't receive YCP command before connection died";
  1743. }
  1744.  
  1745. sub confirm_and_abort {
  1746.     my $ans = UI_YesNo(gettext("Are you sure you want to abandon this set of profile changes and exit?"), "n");
  1747.     if ($ans eq "y") {
  1748.         UI_Info(gettext("Abandoning all changes."));
  1749.         shutdown_yast();
  1750.         exit 0;
  1751.     }
  1752. }
  1753.  
  1754. sub confirm_and_finish {
  1755.     die "FINISHING\n";
  1756. }
  1757.  
  1758. sub build_x_functions($$$) {
  1759.     my ($default, $options, $exec_toggle) = @_;
  1760.     my @{list};
  1761.     if ($exec_toggle) {
  1762.     push @list, "CMD_ix" if $options =~ /i/;
  1763.     push @list, "CMD_pix" if $options =~ /p/ and $options =~ /i/;
  1764.     push @list, "CMD_cix" if $options =~ /c/ and $options =~ /i/;
  1765.     push @list, "CMD_nix" if $options =~ /n/ and $options =~ /i/;
  1766.     push @list, "CMD_ux" if $options =~ /u/;
  1767.     } else {
  1768.     push @list, "CMD_ix" if $options =~ /i/;
  1769.     push @list, "CMD_px" if $options =~ /p/;
  1770.     push @list, "CMD_cx" if $options =~ /c/;
  1771.     push @list, "CMD_nx" if $options =~ /n/;
  1772.     push @list, "CMD_ux" if $options =~ /u/;
  1773.     }
  1774.     if ($exec_toggle) {
  1775.     push @list, "CMD_EXEC_IX_OFF" if $options =~/p|c|n/;
  1776.     } else {
  1777.     push @list, "CMD_EXEC_IX_ON" if $options =~/p|c|n/;
  1778.     }
  1779.     push @list, "CMD_DENY", "CMD_ABORT", "CMD_FINISHED";
  1780.     return @list;
  1781. }
  1782.  
  1783. ##########################################################################
  1784. # this is the hideously ugly function that descends down the flow/event
  1785. # trees that we've generated by parsing the logfile
  1786.  
  1787. sub handlechildren {
  1788.     my $profile = shift;
  1789.     my $hat     = shift;
  1790.     my $root    = shift;
  1791.  
  1792.     my @entries = @$root;
  1793.     for my $entry (@entries) {
  1794.         fatal_error "$entry is not a ref" if not ref($entry);
  1795.  
  1796.         if (ref($entry->[0])) {
  1797.             handlechildren($profile, $hat, $entry);
  1798.         } else {
  1799.  
  1800.             my @entry = @$entry;
  1801.             my $type  = shift @entry;
  1802.  
  1803.             if ($type eq "fork") {
  1804.                 my ($pid, $p, $h) = @entry;
  1805.  
  1806.                 if (   ($p !~ /null(-complain)*-profile/)
  1807.                     && ($h !~ /null(-complain)*-profile/))
  1808.                 {
  1809.                     $profile = $p;
  1810.                     $hat     = $h;
  1811.                 }
  1812.  
  1813.         if ($hat) {
  1814.             $profilechanges{$pid} = $profile . "//" . $hat;
  1815.         } else {
  1816.             $profilechanges{$pid} = $profile;
  1817.         }
  1818.             } elsif ($type eq "unknown_hat") {
  1819.                 my ($pid, $p, $h, $sdmode, $uhat) = @entry;
  1820.  
  1821.                 if ($p !~ /null(-complain)*-profile/) {
  1822.                     $profile = $p;
  1823.                 }
  1824.  
  1825.                 if ($sd{$profile}{$uhat}) {
  1826.                     $hat = $uhat;
  1827.                     next;
  1828.                 }
  1829.  
  1830.                 my $new_p = update_repo_profile($sd{$profile}{$profile});
  1831.                 if ( $new_p and
  1832.                      UI_SelectUpdatedRepoProfile($profile, $new_p) and
  1833.                      $sd{$profile}{$uhat} ) {
  1834.                     $hat = $uhat;
  1835.                     next;
  1836.                 }
  1837.  
  1838.                 # figure out what our default hat for this application is.
  1839.                 my $defaulthat;
  1840.                 for my $hatglob (keys %{$cfg->{defaulthat}}) {
  1841.                     $defaulthat = $cfg->{defaulthat}{$hatglob}
  1842.                       if $profile =~ /$hatglob/;
  1843.                 }
  1844.                 # keep track of previous answers for this run...
  1845.                 my $context = $profile;
  1846.                 $context .= " -> ^$uhat";
  1847.                 my $ans = $transitions{$context} || "XXXINVALIDXXX";
  1848.  
  1849.                 while ($ans !~ /^CMD_(ADDHAT|USEDEFAULT|DENY)$/) {
  1850.                     my $q = {};
  1851.                     $q->{headers} = [];
  1852.                     push @{ $q->{headers} }, gettext("Profile"), $profile;
  1853.                     if ($defaulthat) {
  1854.                         push @{ $q->{headers} }, gettext("Default Hat"), $defaulthat;
  1855.                     }
  1856.                     push @{ $q->{headers} }, gettext("Requested Hat"), $uhat;
  1857.  
  1858.                     $q->{functions} = [];
  1859.                     push @{ $q->{functions} }, "CMD_ADDHAT";
  1860.                     push @{ $q->{functions} }, "CMD_USEDEFAULT" if $defaulthat;
  1861.                     push @{$q->{functions}}, "CMD_DENY", "CMD_ABORT",
  1862.                       "CMD_FINISHED";
  1863.  
  1864.                     $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ADDHAT" : "CMD_DENY";
  1865.  
  1866.                     $seenevents++;
  1867.  
  1868.                     $ans = UI_PromptUser($q);
  1869.  
  1870.                 }
  1871.                 $transitions{$context} = $ans;
  1872.  
  1873.                 if ($ans eq "CMD_ADDHAT") {
  1874.                     $hat = $uhat;
  1875.                     $sd{$profile}{$hat}{flags} = $sd{$profile}{$profile}{flags};
  1876.                 } elsif ($ans eq "CMD_USEDEFAULT") {
  1877.                     $hat = $defaulthat;
  1878.                 } elsif ($ans eq "CMD_DENY") {
  1879.                     return;
  1880.                 }
  1881.  
  1882.             } elsif ($type eq "capability") {
  1883.                my ($pid, $p, $h, $prog, $sdmode, $capability) = @entry;
  1884.  
  1885.                 if (   ($p !~ /null(-complain)*-profile/)
  1886.                     && ($h !~ /null(-complain)*-profile/))
  1887.                 {
  1888.                     $profile = $p;
  1889.                     $hat     = $h;
  1890.                 }
  1891.  
  1892.                 # print "$pid $profile $hat $prog $sdmode capability $capability\n";
  1893.  
  1894.                 next unless $profile && $hat;
  1895.  
  1896.                 $prelog{$sdmode}{$profile}{$hat}{capability}{$capability} = 1;
  1897.             } elsif (($type eq "path") || ($type eq "exec")) {
  1898.                 my ($pid, $p, $h, $prog, $sdmode, $mode, $detail, $to_name) = @entry;
  1899.  
  1900.         $mode = 0 unless ($mode);
  1901.  
  1902.                 if (   ($p !~ /null(-complain)*-profile/)
  1903.                     && ($h !~ /null(-complain)*-profile/))
  1904.                 {
  1905.                     $profile = $p;
  1906.                     $hat     = $h;
  1907.                 }
  1908.  
  1909.                 next unless $profile && $hat;
  1910.                 my $domainchange = ($type eq "exec") ? "change" : "nochange";
  1911.  
  1912.                 # escape special characters that show up in literal paths
  1913.                 $detail =~ s/(\[|\]|\+|\*|\{|\})/\\$1/g;
  1914.  
  1915.                 # we need to give the Execute dialog if they're requesting x
  1916.                 # access for something that's not a directory - we'll force
  1917.                 # a "ix" Path dialog for directories
  1918.                 my $do_execute  = 0;
  1919.                 my $exec_target = $detail;
  1920.  
  1921.                 if ($mode & str_to_mode("x")) {
  1922.                     if (-d $exec_target) {
  1923.             $mode &= (~$ALL_AA_EXEC_TYPE);
  1924.                         $mode |= str_to_mode("ix");
  1925.                     } else {
  1926.                         $do_execute = 1;
  1927.                     }
  1928.                 }
  1929.  
  1930.         if ($mode & $AA_MAY_LINK) {
  1931.                     if ($detail =~ m/^from (.+) to (.+)$/) {
  1932.                         my ($path, $target) = ($1, $2);
  1933.  
  1934.                         my $frommode = str_to_mode("lr");
  1935.                         if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$path}) {
  1936.                             $frommode |= $prelog{$sdmode}{$profile}{$hat}{path}{$path};
  1937.                         }
  1938.                         $prelog{$sdmode}{$profile}{$hat}{path}{$path} = $frommode;
  1939.  
  1940.                         my $tomode = str_to_mode("lr");
  1941.                         if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$target}) {
  1942.                             $tomode |= $prelog{$sdmode}{$profile}{$hat}{path}{$target};
  1943.                         }
  1944.                         $prelog{$sdmode}{$profile}{$hat}{path}{$target} = $tomode;
  1945.  
  1946.                         # print "$pid $profile $hat $prog $sdmode $path:$frommode -> $target:$tomode\n";
  1947.                     } else {
  1948.                         next;
  1949.                     }
  1950.                 } elsif ($mode) {
  1951.                     my $path = $detail;
  1952.  
  1953.                     if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$path}) {
  1954.                         $mode |= $prelog{$sdmode}{$profile}{$hat}{path}{$path};
  1955.                     }
  1956.                     $prelog{$sdmode}{$profile}{$hat}{path}{$path} = $mode;
  1957.  
  1958.                     # print "$pid $profile $hat $prog $sdmode $mode $path\n";
  1959.                 }
  1960.  
  1961.                 if ($do_execute) {
  1962.                     next if ( profile_known_exec($sd{$profile}{$hat},
  1963.                          "exec", $exec_target ) );
  1964.  
  1965.                     my $p = update_repo_profile($sd{$profile}{$profile});
  1966.  
  1967.             if ($to_name) {
  1968.             next if ( $to_name and
  1969.                   UI_SelectUpdatedRepoProfile($profile, $p) and
  1970.                   profile_known_exec($sd{$profile}{$hat},
  1971.                              "exec", $to_name ) );
  1972.             } else {
  1973.             next if ( UI_SelectUpdatedRepoProfile($profile, $p) and
  1974.                   profile_known_exec($sd{$profile}{$hat},
  1975.                              "exec", $exec_target ) );
  1976.             }
  1977.  
  1978.                     my $context = $profile;
  1979.                     $context .= "^$hat" if $profile ne $hat;
  1980.                     $context .= " -> $exec_target";
  1981.                     my $ans = $transitions{$context} || "";
  1982.  
  1983.                     my ($combinedmode, $combinedaudit, $cm, $am, @m);
  1984.             $combinedmode = 0;
  1985.             $combinedaudit = 0;
  1986.  
  1987.                     # does path match any regexps in original profile?
  1988.                     ($cm, $am, @m) = rematchfrag($sd{$profile}{$hat}, 'allow', $exec_target);
  1989.                     $combinedmode |= $cm if $cm;
  1990.             $combinedaudit |= $am if $am;
  1991.  
  1992.             # find the named transition if is present
  1993.             if ($combinedmode & str_to_mode("x")) {
  1994.             my $nt_name;
  1995.             foreach my $entry (@m) {
  1996.                 if ($sd{$profile}{$hat}{allow}{path}{$entry}{to}) {
  1997.                 $nt_name = $sd{$profile}{$hat}{allow}{path}{$entry}{to};
  1998.                 last;
  1999.                 }
  2000.             }
  2001.             if ($to_name and $nt_name and ($to_name ne $nt_name)) {
  2002.                 #fatal_error "transition name from "
  2003.             } elsif ($nt_name) {
  2004.                 $to_name = $nt_name;
  2005.             }
  2006.             }
  2007.  
  2008.                     # does path match anything pulled in by includes in
  2009.                     # original profile?
  2010.                     ($cm, $am, @m) = match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $exec_target);
  2011.                     $combinedmode |= $cm if $cm;
  2012.                     $combinedaudit |= $am if $am;
  2013.             if ($combinedmode & str_to_mode("x")) {
  2014.             my $nt_name;
  2015.             foreach my $entry (@m) {
  2016.                 if ($sd{$profile}{$hat}{allow}{path}{$entry}{to}) {
  2017.                 $nt_name = $sd{$profile}{$hat}{allow}{path}{$entry}{to};
  2018.                 last;
  2019.                 }
  2020.             }
  2021.             if ($to_name and $nt_name and ($to_name ne $nt_name)) {
  2022.                 #fatal_error "transition name from "
  2023.             } elsif ($nt_name) {
  2024.                 $to_name = $nt_name;
  2025.             }
  2026.             }
  2027.  
  2028.  
  2029.             #nx does not exist in profiles.  It does in log
  2030.             #files however.  The log parsing routines will convert
  2031.             #it to its profile form.
  2032.             #nx is internally represented by cx/px/cix/pix + to_name
  2033.                     my $exec_mode = 0;
  2034.             if (contains($combinedmode, "pix")) {
  2035.             if ($to_name) {
  2036.                 $ans = "CMD_nix";
  2037.             } else {
  2038.                 $ans = "CMD_pix";
  2039.             }
  2040.             $exec_mode = str_to_mode("pixr");
  2041.             } elsif (contains($combinedmode, "cix")) {
  2042.             if ($to_name) {
  2043.                 $ans = "CMD_nix";
  2044.             } else {
  2045.                 $ans = "CMD_cix";
  2046.             }
  2047.             $exec_mode = str_to_mode("cixr");
  2048.             } elsif (contains($combinedmode, "Pix")) {
  2049.             if ($to_name) {
  2050.                 $ans = "CMD_nix_safe";
  2051.             } else {
  2052.                 $ans = "CMD_pix_safe";
  2053.             }
  2054.             $exec_mode = str_to_mode("Pixr");
  2055.             } elsif (contains($combinedmode, "Cix")) {
  2056.             if ($to_name) {
  2057.                 $ans = "CMD_nix_safe";
  2058.             } else {
  2059.                 $ans = "CMD_cix_safe";
  2060.             }
  2061.             $exec_mode = str_to_mode("Cixr");
  2062.             } elsif (contains($combinedmode, "ix")) {
  2063.                         $ans       = "CMD_ix";
  2064.                         $exec_mode = str_to_mode("ixr");
  2065.                     } elsif (contains($combinedmode, "px")) {
  2066.             if ($to_name) {
  2067.                 $ans = "CMD_nx";
  2068.             } else {
  2069.                 $ans = "CMD_px";
  2070.             }
  2071.                         $exec_mode = str_to_mode("px");
  2072.             } elsif (contains($combinedmode, "cx")) {
  2073.             if ($to_name) {
  2074.                 $ans = "CMD_nx";
  2075.             } else {
  2076.                 $ans = "CMD_cx";
  2077.             }
  2078.             $exec_mode = str_to_mode("cx");
  2079.                     } elsif (contains($combinedmode, "ux")) {
  2080.                         $ans       = "CMD_ux";
  2081.                         $exec_mode = str_to_mode("ux");
  2082.                     } elsif (contains($combinedmode, "Px")) {
  2083.             if ($to_name) {
  2084.                 $ans = "CMD_nx_safe";
  2085.             } else {
  2086.                 $ans       = "CMD_px_safe";
  2087.             }
  2088.                         $exec_mode = str_to_mode("Px");
  2089.             } elsif (contains($combinedmode, "Cx")) {
  2090.             if ($to_name) {
  2091.                 $ans = "CMD_nx_safe";
  2092.             } else {
  2093.                 $ans = "CMD_cx_safe";
  2094.             }
  2095.             $exec_mode = str_to_mode("Cx");
  2096.                     } elsif (contains($combinedmode, "Ux")) {
  2097.                         $ans       = "CMD_ux_safe";
  2098.                         $exec_mode = str_to_mode("Ux");
  2099.                     } else {
  2100.                         my $options = $cfg->{qualifiers}{$exec_target} || "ipcnu";
  2101.             fatal_error "$entry has transition name but not transition mode" if $to_name;
  2102.  
  2103.                         # force "ix" as the only option when the profiled
  2104.                         # program executes itself
  2105.                         $options = "i" if $exec_target eq $profile;
  2106.  
  2107.             # for now don't allow hats to cx
  2108.             $options =~ s/c// if $hat and $hat ne $profile;
  2109.  
  2110.                         # we always need deny...
  2111.                         $options .= "d";
  2112.  
  2113.                         # figure out what our default option should be...
  2114.                         my $default;
  2115.                         if ($options =~ /p/
  2116.                             && -e getprofilefilename($exec_target))
  2117.                         {
  2118.                             $default = "CMD_px";
  2119.                         } elsif ($options =~ /i/) {
  2120.                             $default = "CMD_ix";
  2121.                         } elsif ($options =~ /c/) {
  2122.                             $default = "CMD_cx";
  2123.                         } elsif ($options =~ /n/) {
  2124.                             $default = "CMD_nx";
  2125.                         } else {
  2126.                             $default = "CMD_DENY";
  2127.                         }
  2128.  
  2129.                         # ugh, this doesn't work if someone does an ix before
  2130.                         # calling this particular child process.  at least
  2131.                         # it's only a hint instead of mandatory to get this
  2132.                         # right.
  2133.                         my $parent_uses_ld_xxx = check_for_LD_XXX($profile);
  2134.  
  2135.                         my $severity = $sevdb->rank($exec_target, "x");
  2136.  
  2137.                         # build up the prompt...
  2138.                         my $q = {};
  2139.                         $q->{headers} = [];
  2140.                         push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  2141.                         if ($prog && $prog ne "HINT") {
  2142.                             push @{ $q->{headers} }, gettext("Program"), $prog;
  2143.                         }
  2144.             # $to_name should NOT exist here other wise we know what
  2145.             # mode we are supposed to be transitioning to
  2146.             # which is handled above.
  2147.                         push @{ $q->{headers} }, gettext("Execute"),  $exec_target;
  2148.                         push @{ $q->{headers} }, gettext("Severity"), $severity;
  2149.  
  2150.                         $q->{functions} = [];
  2151.  
  2152.                         my $prompt = "\n$context\n";
  2153.             my $exec_toggle = 0;
  2154.  
  2155.             push @{ $q->{functions} }, build_x_functions($default, $options, $exec_toggle);
  2156.  
  2157.                         $options = join("|", split(//, $options));
  2158.  
  2159.                         $seenevents++;
  2160.  
  2161.             while ($ans !~ m/^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$/) {
  2162.                 $ans = UI_PromptUser($q);
  2163.  
  2164.                 if ($ans =~ /CMD_EXEC_IX_/) {
  2165.                 $exec_toggle = !$exec_toggle;
  2166.  
  2167.                 $q->{functions} = [ ];
  2168.                 push @{ $q->{functions} }, build_x_functions($default, $options, $exec_toggle);
  2169.                 $ans = "";
  2170.                 next;
  2171.                 }
  2172.                 if ($ans =~ /CMD_(nx|nix)/) {
  2173.                                 my $arg = $exec_target;
  2174.  
  2175.                 my $ynans = "n";
  2176.                 if ($profile eq $hat) {
  2177.                     $ynans = UI_YesNo("Are you specifying a transition to a local profile?", "n");
  2178.                 }
  2179.  
  2180.                 if ($ynans eq "y") {
  2181.                     if ($ans eq "CMD_nx") {
  2182.                     $ans = "CMD_cx";
  2183.                     } else {
  2184.                     $ans = "CMD_cix";
  2185.                     }
  2186.                 } else {
  2187.                     if ($ans eq "CMD_nx") {
  2188.                     $ans = "CMD_px";
  2189.                     } else {
  2190.                     $ans = "CMD_pix";
  2191.                     }
  2192.                 }
  2193.                 $to_name = UI_GetString(gettext("Enter profile name to transition to: "), $arg);
  2194.                 }
  2195.                 if ($ans =~ /CMD_ix/) {
  2196.                 $exec_mode = str_to_mode("ix");
  2197.                             } elsif ($ans =~ /CMD_(px|cx|nx|pix|cix|nix)/) {
  2198.                 my $match = $1;
  2199.                 $exec_mode = str_to_mode($match);
  2200.                                 my $px_default = "n";
  2201.                                 my $px_mesg    = gettext("Should AppArmor sanitize the environment when\nswitching profiles?\n\nSanitizing the environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.");
  2202.                                 if ($parent_uses_ld_xxx) {
  2203.                                     $px_mesg = gettext("Should AppArmor sanitize the environment when\nswitching profiles?\n\nSanitizing the environment is more secure,\nbut this application appears to use LD_PRELOAD\nor LD_LIBRARY_PATH and clearing these could\ncause functionality problems.");
  2204.                                 }
  2205.                                 my $ynans = UI_YesNo($px_mesg, $px_default);
  2206.                 $ans = "CMD_$match";
  2207.                                 if ($ynans eq "y") {
  2208.                                     $exec_mode &= ~$AA_EXEC_UNSAFE;
  2209.                                 }
  2210.                             } elsif ($ans eq "CMD_ux") {
  2211.                 $exec_mode = str_to_mode("ux");
  2212.                                 my $ynans = UI_YesNo(sprintf(gettext("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing \%s?"), $exec_target), "n");
  2213.                                 if ($ynans eq "y") {
  2214.                                     my $ynans = UI_YesNo(gettext("Should AppArmor sanitize the environment when\nrunning this program unconfined?\n\nNot sanitizing the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), "y");
  2215.                                     if ($ynans eq "y") {
  2216.                     $exec_mode &= ~($AA_EXEC_UNSAFE | ($AA_EXEC_UNSAFE << $AA_OTHER_SHIFT));
  2217.                                     }
  2218.                                 } else {
  2219.                                     $ans = "INVALID";
  2220.                                 }
  2221.                             }
  2222.                         }
  2223.                         $transitions{$context} = $ans;
  2224.  
  2225.             if ($ans =~ /CMD_(ix|px|cx|nx|pix|cix|nix)/) {
  2226.                 # if we're inheriting, things'll bitch unless we have r
  2227.                 if ($exec_mode & str_to_mode("i")) {
  2228.                 $exec_mode |= str_to_mode("r");
  2229.                 }
  2230.  
  2231.             } else {
  2232.                 if ($ans eq "CMD_DENY") {
  2233.                 $sd{$profile}{$hat}{deny}{path}{$exec_target}{mode} |= str_to_mode("x");
  2234.  
  2235.                 $sd{$profile}{$hat}{deny}{path}{$exec_target}{audit} |= 0;
  2236.                 $changed{$profile} = 1;
  2237.                 }
  2238.  
  2239.                             # skip all remaining events if they say to deny
  2240.                             # the exec
  2241.                             return if $domainchange eq "change";
  2242.                         }
  2243.  
  2244.             unless ($ans eq "CMD_DENY") {
  2245. # ???? if its defined in the prelog we shouldn't have asked
  2246.                             if (defined $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target}) {
  2247. #                                $exec_mode = $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target};
  2248.                             }
  2249.                             $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target} |= $exec_mode;
  2250.                             $log{PERMITTING}{$profile}              = {};
  2251.                             $sd{$profile}{$hat}{allow}{path}{$exec_target}{mode} = $exec_mode;
  2252.                             $sd{$profile}{$hat}{allow}{path}{$exec_target}{audit} |= 0;
  2253.                             $sd{$profile}{$hat}{allow}{path}{$exec_target}{to} = $to_name if ($to_name);
  2254.  
  2255.                             # mark this profile as changed
  2256.                             $changed{$profile} = 1;
  2257.  
  2258.                             if ($exec_mode & str_to_mode("i")) {
  2259.                                 if ($exec_target =~ /perl/) {
  2260.                                     $sd{$profile}{$hat}{include}{"abstractions/perl"} = 1;
  2261.                                 } elsif ($detail =~ m/\/bin\/(bash|sh)/) {
  2262.                                     $sd{$profile}{$hat}{include}{"abstractions/bash"} = 1;
  2263.                                 }
  2264.                                 my $hashbang = head($exec_target);
  2265.                                 if ($hashbang =~ /^#!\s*(\S+)/) {
  2266.                                     my $interpreter = get_full_path($1);
  2267.                                     $sd{$profile}{$hat}{path}->{$interpreter}{mode} = str_to_mode("ix");
  2268.                                     $sd{$profile}{$hat}{path}->{$interpreter}{audit} |= 0;
  2269.                                     if ($interpreter =~ /perl/) {
  2270.                                         $sd{$profile}{$hat}{include}{"abstractions/perl"} = 1;
  2271.                                     } elsif ($interpreter =~ m/\/bin\/(bash|sh)/) {
  2272.                                         $sd{$profile}{$hat}{include}{"abstractions/bash"} = 1;
  2273.                                     }
  2274.                                 }
  2275.                             }
  2276.                         }
  2277.             }
  2278.  
  2279.                     # print "$pid $profile $hat EXEC $exec_target $ans $exec_mode\n";
  2280.  
  2281.                     # update our tracking info based on what kind of change
  2282.                     # this is...
  2283.                     if ($ans eq "CMD_ix") {
  2284.             if ($hat) {
  2285.                 $profilechanges{$pid} = $profile . "//" . $hat;
  2286.             } else {
  2287.                 $profilechanges{$pid} = $profile;
  2288.             }
  2289.                     } elsif ($ans =~ /^CMD_(px|nx|pix|nix)/) {
  2290.             $exec_target = $to_name if ($to_name);
  2291.                         if ($sdmode eq "PERMITTING") {
  2292.                             if ($domainchange eq "change") {
  2293.                                 $profile              = $exec_target;
  2294.                                 $hat                  = $exec_target;
  2295.                                 $profilechanges{$pid} = $profile;
  2296.                             }
  2297.                         }
  2298.                         # if they want to use px, make sure a profile
  2299.                         # exists for the target.
  2300.                         unless (-e getprofilefilename($exec_target)) {
  2301.                 my $ynans = "y";
  2302.                 if ($exec_mode & str_to_mode("i")) {
  2303.                 $ynans = UI_YesNo(sprintf(gettext("A profile for %s does not exist create one?"), $exec_target), "n");
  2304.                 }
  2305.                 if ($ynans eq "y") {
  2306.                 $helpers{$exec_target} = "enforce";
  2307.                 if ($to_name) {
  2308.                     autodep_base("", $exec_target);
  2309.                 } else {
  2310.                     autodep_base($exec_target, "");
  2311.                 }
  2312.                 reload_base($exec_target);
  2313.                 }
  2314.                         }
  2315.                     } elsif ($ans =~ /^CMD_(cx|cix)/) {
  2316.             $exec_target = $to_name if ($to_name);
  2317.                         if ($sdmode eq "PERMITTING") {
  2318.                             if ($domainchange eq "change") {
  2319.                                 $profilechanges{$pid} = "${profile}//${exec_target}";
  2320. #                                $profile              = $exec_target;
  2321. #                                $hat                  = $exec_target;
  2322.                             }
  2323.                         }
  2324.  
  2325.                         # if they want to use cx, make sure a profile
  2326.                         # exists for the target.
  2327.             unless ($sd{$profile}{$exec_target}) {
  2328.                 my $ynans = "y";
  2329.                 if ($exec_mode & str_to_mode("i")) {
  2330.                 $ynans = UI_YesNo(sprintf(gettext("A local profile for %s does not exist create one?"), $exec_target), "n");
  2331.                 }
  2332.                 if ($ynans eq "y") {
  2333.                 $hat = $exec_target;
  2334.                 # keep track of profile flags
  2335.                 #$profile_data->{$profile}{$hat}{flags} = ;
  2336.  
  2337.                 # we have seen more than a declaration so clear it
  2338.                 $sd{$profile}{$hat}{'declared'} = 0;
  2339.                 $sd{$profile}{$hat}{profile} = 1;
  2340.                 $sd{$profile}{$hat}{allow}{path} = { };
  2341.                 $sd{$profile}{$hat}{allow}{netdomain} = { };
  2342.                 my $file = $sd{$profile}{$profile}{filename};
  2343.                 $filelist{$file}{profiles}{$profile}{$hat} = 1;
  2344.  
  2345.                 }
  2346.                         }
  2347.                     } elsif ($ans =~ /^CMD_ux/) {
  2348.                         $profilechanges{$pid} = "unconstrained";
  2349.                         return if $domainchange eq "change";
  2350.                     }
  2351.                 }
  2352.             } elsif ( $type eq "netdomain" ) {
  2353.                my ($pid, $p, $h, $prog, $sdmode, $family, $sock_type, $protocol) =
  2354.                   @entry;
  2355.  
  2356.                 if (   ($p !~ /null(-complain)*-profile/)
  2357.                     && ($h !~ /null(-complain)*-profile/))
  2358.                 {
  2359.                     $profile = $p;
  2360.                     $hat     = $h;
  2361.                 }
  2362.  
  2363.                 next unless $profile && $hat;
  2364.                 $prelog{$sdmode}
  2365.                        {$profile}
  2366.                        {$hat}
  2367.                        {netdomain}
  2368.                        {$family}
  2369.                        {$sock_type} = 1 unless ( !$family || !$sock_type );
  2370.  
  2371.             }
  2372.         }
  2373.     }
  2374. }
  2375.  
  2376. sub add_to_tree ($@) {
  2377.     my ($pid, $type, @event) = @_;
  2378.     if ( $DEBUGGING ) {
  2379.         my $eventmsg = Data::Dumper->Dump([@event], [qw(*event)]);
  2380.         $eventmsg =~ s/\n/ /g;
  2381.         debug " add_to_tree: pid [$pid] type [$type] event [ $eventmsg ]";
  2382.     }
  2383.  
  2384.     unless (exists $pid{$pid}) {
  2385.         my $arrayref = [];
  2386.         push @log, $arrayref;
  2387.         $pid{$pid} = $arrayref;
  2388.     }
  2389.  
  2390.     push @{ $pid{$pid} }, [ $type, $pid, @event ];
  2391. }
  2392.  
  2393. #
  2394. # variables used in the logparsing routines
  2395. #
  2396. our $LOG;
  2397. our $next_log_entry;
  2398. our $logmark;
  2399. our $seenmark;
  2400. my $RE_LOG_v2_0_syslog = qr/SubDomain/;
  2401. my $RE_LOG_v2_1_syslog = qr/kernel:\s+(\[[\d\.\s]+\]\s+)?audit\([\d\.\:]+\):\s+type=150[1-6]/;
  2402. my $RE_LOG_v2_0_audit  =
  2403.     qr/type=(APPARMOR|UNKNOWN\[1500\]) msg=audit\([\d\.\:]+\):/;
  2404. my $RE_LOG_v2_1_audit  =
  2405.     qr/type=(UNKNOWN\[150[1-6]\]|APPARMOR_(AUDIT|ALLOWED|DENIED|HINT|STATUS|ERROR))/;
  2406.  
  2407. sub prefetch_next_log_entry {
  2408.     # if we already have an existing cache entry, something's broken
  2409.     if ($next_log_entry) {
  2410.         print STDERR "Already had next log entry: $next_log_entry";
  2411.     }
  2412.  
  2413.     # read log entries until we either hit the end or run into an
  2414.     # AA event message format we recognize
  2415.     do {
  2416.         $next_log_entry = <$LOG>;
  2417.     } until (!$next_log_entry || $next_log_entry =~ m{
  2418.         $RE_LOG_v2_0_syslog |
  2419.         $RE_LOG_v2_0_audit  |
  2420.         $RE_LOG_v2_1_audit  |
  2421.         $RE_LOG_v2_1_syslog |
  2422.         $logmark
  2423.     }x);
  2424. }
  2425.  
  2426. sub get_next_log_entry {
  2427.     # make sure we've got a next log entry if there is one
  2428.     prefetch_next_log_entry() unless $next_log_entry;
  2429.  
  2430.     # save a copy of the next log entry...
  2431.     my $log_entry = $next_log_entry;
  2432.  
  2433.     # zero out our cache of the next log entry
  2434.     $next_log_entry = undef;
  2435.  
  2436.     return $log_entry;
  2437. }
  2438.  
  2439. sub peek_at_next_log_entry {
  2440.     # make sure we've got a next log entry if there is one
  2441.     prefetch_next_log_entry() unless $next_log_entry;
  2442.  
  2443.     # return a copy of the next log entry without pulling it out of the cache
  2444.     return $next_log_entry;
  2445. }
  2446.  
  2447. sub throw_away_next_log_entry {
  2448.     $next_log_entry = undef;
  2449. }
  2450.  
  2451. sub parse_log_record_v_2_0 ($@) {
  2452.     my ($record, $last) = @_;
  2453.     $DEBUGGING && debug "parse_log_record_v_2_0: $record";
  2454.  
  2455.     # What's this early out for?  As far as I can tell, parse_log_record_v_2_0
  2456.     # won't ever be called without something in $record
  2457.     return $last if ( ! $record );
  2458.  
  2459.     $_ = $record;
  2460.  
  2461.     if (s/(PERMITTING|REJECTING)-SYSLOGFIX/$1/) {
  2462.         s/%%/%/g;
  2463.     }
  2464.  
  2465.     if (m/LOGPROF-HINT unknown_hat (\S+) pid=(\d+) profile=(.+) active=(.+)/) {
  2466.         my ($uhat, $pid, $profile, $hat) = ($1, $2, $3, $4);
  2467.  
  2468.         $last = $&;
  2469.  
  2470.         # we want to ignore entries for profiles that don't exist
  2471.         # they're most likely broken entries or old entries for
  2472.         # deleted profiles
  2473.         return $&
  2474.           if ( ($profile ne 'null-complain-profile')
  2475.             && (!profile_exists($profile)));
  2476.  
  2477.         add_to_tree($pid, "unknown_hat", $profile, $hat,
  2478.                     "PERMITTING", $uhat);
  2479.     } elsif (m/LOGPROF-HINT (unknown_profile|missing_mandatory_profile) image=(.+) pid=(\d+) profile=(.+) active=(.+)/) {
  2480.         my ($image, $pid, $profile, $hat) = ($2, $3, $4, $5);
  2481.  
  2482.         return $& if $last =~ /PERMITTING x access to $image/;
  2483.         $last = $&;
  2484.  
  2485.         # we want to ignore entries for profiles that don't exist
  2486.         # they're most likely broken entries or old entries for
  2487.         # deleted profiles
  2488.         return $&
  2489.           if ( ($profile ne 'null-complain-profile')
  2490.             && (!profile_exists($profile)));
  2491.  
  2492.         add_to_tree($pid, "exec", $profile, $hat, "HINT", "PERMITTING", "x", $image);
  2493.  
  2494.     } elsif (m/(PERMITTING|REJECTING) (\S+) access (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2495.         my ($sdmode, $mode, $detail, $prog, $pid, $profile, $hat) =
  2496.            ($1, $2, $3, $4, $5, $6, $7);
  2497.  
  2498.     if ($mode eq "link") {
  2499.         $mode = "l";
  2500.     }
  2501.         if (!validate_log_mode($mode)) {
  2502.             fatal_error(sprintf(gettext('Log contains unknown mode %s.'), $mode));
  2503.         }
  2504.  
  2505.         my $domainchange = "nochange";
  2506.         if ($mode =~ /x/) {
  2507.  
  2508.             # we need to try to check if we're doing a domain transition
  2509.             if ($sdmode eq "PERMITTING") {
  2510.                 my $following = peek_at_next_log_entry();
  2511.  
  2512.                 if ($following && ($following =~ m/changing_profile/)) {
  2513.                     $domainchange = "change";
  2514.                     throw_away_next_log_entry();
  2515.                 }
  2516.             }
  2517.         } else {
  2518.  
  2519.             # we want to ignore duplicates for things other than executes...
  2520.             return $& if $seen{$&};
  2521.             $seen{$&} = 1;
  2522.         }
  2523.  
  2524.         $last = $&;
  2525.  
  2526.         # we want to ignore entries for profiles that don't exist
  2527.         # they're most likely broken entries or old entries for
  2528.         # deleted profiles
  2529.         if (($profile ne 'null-complain-profile')
  2530.             && (!profile_exists($profile)))
  2531.         {
  2532.             return $&;
  2533.         }
  2534.  
  2535.         # currently no way to stick pipe mediation in a profile, ignore
  2536.         # any messages like this
  2537.         return $& if $detail =~ /to pipe:/;
  2538.  
  2539.         # strip out extra extended attribute info since we don't
  2540.         # currently have a way to specify it in the profile and
  2541.         # instead just need to provide the access to the base filename
  2542.         $detail =~ s/\s+extended attribute \S+//;
  2543.  
  2544.         # kerberos code checks to see if the krb5.conf file is world
  2545.         # writable in a stupid way so we'll ignore any w accesses to
  2546.         # krb5.conf
  2547.         return $& if (($detail eq "to /etc/krb5.conf") && contains($mode, "w"));
  2548.  
  2549.         # strip off the (deleted) tag that gets added if it's a
  2550.         # deleted file
  2551.         $detail =~ s/\s+\(deleted\)$//;
  2552.  
  2553.     #            next if (($detail =~ /to \/lib\/ld-/) && ($mode =~ /x/));
  2554.  
  2555.         $detail =~ s/^to\s+//;
  2556.  
  2557.         if ($domainchange eq "change") {
  2558.             add_to_tree($pid, "exec", $profile, $hat, $prog,
  2559.                         $sdmode, str_to_mode($mode), $detail);
  2560.         } else {
  2561.             add_to_tree($pid, "path", $profile, $hat, $prog,
  2562.                         $sdmode, str_to_mode($mode), $detail);
  2563.         }
  2564.  
  2565.     } elsif (m/(PERMITTING|REJECTING) (?:mk|rm)dir on (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2566.         my ($sdmode, $path, $prog, $pid, $profile, $hat) =
  2567.            ($1, $2, $3, $4, $5, $6);
  2568.  
  2569.         # we want to ignore duplicates for things other than executes...
  2570.         return $& if $seen{$&}++;
  2571.  
  2572.         $last = $&;
  2573.  
  2574.         # we want to ignore entries for profiles that don't exist
  2575.         # they're most likely broken entries or old entries for
  2576.         # deleted profiles
  2577.         return $&
  2578.           if ( ($profile ne 'null-complain-profile')
  2579.             && (!profile_exists($profile)));
  2580.  
  2581.         add_to_tree($pid, "path", $profile, $hat, $prog, $sdmode,
  2582.                     "w", $path);
  2583.  
  2584.     } elsif (m/(PERMITTING|REJECTING) xattr (\S+) on (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2585.         my ($sdmode, $xattr_op, $path, $prog, $pid, $profile, $hat) =
  2586.            ($1, $2, $3, $4, $5, $6, $7);
  2587.  
  2588.         # we want to ignore duplicates for things other than executes...
  2589.         return $& if $seen{$&}++;
  2590.  
  2591.         $last = $&;
  2592.  
  2593.         # we want to ignore entries for profiles that don't exist
  2594.         # they're most likely broken entries or old entries for
  2595.         # deleted profiles
  2596.         return $&
  2597.           if ( ($profile ne 'null-complain-profile')
  2598.             && (!profile_exists($profile)));
  2599.  
  2600.         my $xattrmode;
  2601.         if ($xattr_op eq "get" || $xattr_op eq "list") {
  2602.             $xattrmode = "r";
  2603.         } elsif ($xattr_op eq "set" || $xattr_op eq "remove") {
  2604.             $xattrmode = "w";
  2605.         }
  2606.  
  2607.         if ($xattrmode) {
  2608.             add_to_tree($pid, "path", $profile, $hat, $prog, $sdmode,
  2609.                         str_to_mode($xattrmode), $path);
  2610.         }
  2611.  
  2612.     } elsif (m/(PERMITTING|REJECTING) attribute \((.*?)\) change to (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2613.         my ($sdmode, $change, $path, $prog, $pid, $profile, $hat) =
  2614.            ($1, $2, $3, $4, $5, $6, $7);
  2615.  
  2616.         # we want to ignore duplicates for things other than executes...
  2617.         return $& if $seen{$&};
  2618.         $seen{$&} = 1;
  2619.  
  2620.         $last = $&;
  2621.  
  2622.         # we want to ignore entries for profiles that don't exist
  2623.         # they're most likely broken entries or old entries for
  2624.         # deleted profiles
  2625.         return $&
  2626.           if ( ($profile ne 'null-complain-profile')
  2627.             && (!profile_exists($profile)));
  2628.  
  2629.         # kerberos code checks to see if the krb5.conf file is world
  2630.         # writable in a stupid way so we'll ignore any w accesses to
  2631.         # krb5.conf
  2632.         return $& if $path eq "/etc/krb5.conf";
  2633.  
  2634.         add_to_tree($pid, "path", $profile, $hat, $prog, $sdmode,
  2635.                     str_to_mode("w"), $path);
  2636.  
  2637.     } elsif (m/(PERMITTING|REJECTING) access to capability '(\S+)' \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2638.         my ($sdmode, $capability, $prog, $pid, $profile, $hat) =
  2639.            ($1, $2, $3, $4, $5, $6);
  2640.  
  2641.         return $& if $seen{$&};
  2642.  
  2643.         $seen{$&} = 1;
  2644.         $last = $&;
  2645.  
  2646.         # we want to ignore entries for profiles that don't exist - they're
  2647.         # most likely broken entries or old entries for deleted profiles
  2648.         return $&
  2649.           if ( ($profile ne 'null-complain-profile')
  2650.             && (!profile_exists($profile)));
  2651.  
  2652.         add_to_tree($pid, "capability", $profile, $hat, $prog,
  2653.                     $sdmode, $capability);
  2654.  
  2655.     } elsif (m/Fork parent (\d+) child (\d+) profile (.+) active (.+)/
  2656.         || m/LOGPROF-HINT fork pid=(\d+) child=(\d+) profile=(.+) active=(.+)/
  2657.         || m/LOGPROF-HINT fork pid=(\d+) child=(\d+)/)
  2658.     {
  2659.         my ($parent, $child, $profile, $hat) = ($1, $2, $3, $4);
  2660.  
  2661.         $profile ||= "null-complain-profile";
  2662.         $hat     ||= "null-complain-profile";
  2663.  
  2664.         $last = $&;
  2665.  
  2666.         # we want to ignore entries for profiles that don't exist
  2667.         # they're  most likely broken entries or old entries for
  2668.         # deleted profiles
  2669.         return $&
  2670.           if ( ($profile ne 'null-complain-profile')
  2671.             && (!profile_exists($profile)));
  2672.  
  2673.         my $arrayref = [];
  2674.         if (exists $pid{$parent}) {
  2675.             push @{ $pid{$parent} }, $arrayref;
  2676.         } else {
  2677.             push @log, $arrayref;
  2678.         }
  2679.         $pid{$child} = $arrayref;
  2680.         push @{$arrayref}, [ "fork", $child, $profile, $hat ];
  2681.     } else {
  2682.         $DEBUGGING && debug "UNHANDLED: $_";
  2683.     }
  2684.     return $last;
  2685. }
  2686.  
  2687. sub parse_log_record ($) {
  2688.     my $record = shift;
  2689.     $DEBUGGING && debug "parse_log_record: $record";
  2690.     my $e = parse_event($record);
  2691.  
  2692.     return $e;
  2693. }
  2694.  
  2695.  
  2696. sub add_event_to_tree ($) {
  2697.     my $e = shift;
  2698.  
  2699.     my $sdmode = $e->{sdmode}?$e->{sdmode}:"UNKNOWN";
  2700.     if ( $e->{type} ) {
  2701.         if ( $e->{type} =~ /(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)/ ) {
  2702.             $sdmode = "AUDIT";
  2703.         } elsif ( $e->{type} =~ /(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)/ ) {
  2704.             $sdmode = "PERMITTING";
  2705.         } elsif ( $e->{type} =~ /(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)/ ) {
  2706.             $sdmode = "REJECTING";
  2707.         } elsif ( $e->{type} =~ /(UNKNOWN\[1504\]|APPARMOR_HINT|1504)/ ) {
  2708.             $sdmode = "HINT";
  2709.         } elsif ( $e->{type} =~ /(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)/ ) {
  2710.             $sdmode = "STATUS";
  2711.         } elsif ( $e->{type} =~ /(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)/ ) {
  2712.             $sdmode = "ERROR";
  2713.         } else {
  2714.             $sdmode = "UNKNOWN";
  2715.         }
  2716.     }
  2717.     return if ( $sdmode =~ /UNKNOWN|AUDIT|STATUS|ERROR/ );
  2718.  
  2719.     my ($profile, $hat);
  2720.     ($profile, $hat) = split /\/\//, $e->{profile};
  2721.     if ( $e->{operation} eq "change_hat" ) {
  2722.         ($profile, $hat) = split /\/\//, $e->{name};
  2723.     }
  2724.     $hat = $profile if ( !$hat );
  2725.     # TODO - refactor add_to_tree as prog is no longer supplied
  2726.     #        HINT is from previous format where prog was not
  2727.     #        consistently passed
  2728.     my $prog = "HINT";
  2729.  
  2730.     return if ($profile ne 'null-complain-profile' && !profile_exists($profile));
  2731.  
  2732.     if ($e->{operation} eq "exec") {
  2733.         if ( defined $e->{info} && $e->{info} eq "mandatory profile missing" ) {
  2734.             add_to_tree( $e->{pid},
  2735.                          "exec",
  2736.                          $profile,
  2737.                          $hat,
  2738.                          $sdmode,
  2739.                          "PERMITTING",
  2740.                          $e->{denied_mask},
  2741.                          $e->{name},
  2742.                          $e->{name2}
  2743.                        );
  2744.         }
  2745.     } elsif ($e->{operation} =~ m/file_/) {
  2746.         add_to_tree( $e->{pid},
  2747.                      "path",
  2748.                      $profile,
  2749.                      $hat,
  2750.                      $prog,
  2751.                      $sdmode,
  2752.                      $e->{denied_mask},
  2753.                      $e->{name},
  2754.              "",
  2755.                    );
  2756.     } elsif ($e->{operation} eq "capable") {
  2757.         add_to_tree( $e->{pid},
  2758.                      "capability",
  2759.                      $profile,
  2760.                      $hat,
  2761.                      $prog,
  2762.                      $sdmode,
  2763.                      $e->{name}
  2764.                    );
  2765.     } elsif ($e->{operation} =~  m/xattr/ ||
  2766.              $e->{operation} eq "setattr") {
  2767.         add_to_tree( $e->{pid},
  2768.                      "path",
  2769.                      $profile,
  2770.                      $hat,
  2771.                      $prog,
  2772.                      $sdmode,
  2773.                      $e->{denied_mask},
  2774.                      $e->{name},
  2775.              ""
  2776.                     );
  2777.     } elsif ($e->{operation} =~ m/inode_/) {
  2778.         my $is_domain_change = 0;
  2779.  
  2780.         if ($e->{operation}   eq "inode_permission" &&
  2781.             $e->{denied_mask} & $AA_MAY_EXEC                &&
  2782.             $sdmode           eq "PERMITTING") {
  2783.  
  2784.             my $following = peek_at_next_log_entry();
  2785.             if ($following) {
  2786.                 my $entry = parse_log_record($following);
  2787.                 if ($entry &&
  2788.                     $entry->{info} &&
  2789.                     $entry->{info} eq "set profile" ) {
  2790.  
  2791.                     $is_domain_change = 1;
  2792.                     throw_away_next_log_entry();
  2793.                 }
  2794.             }
  2795.         }
  2796.  
  2797.         if ($is_domain_change) {
  2798.             add_to_tree( $e->{pid},
  2799.                           "exec",
  2800.                           $profile,
  2801.                           $hat,
  2802.                           $prog,
  2803.                           $sdmode,
  2804.                           $e->{denied_mask},
  2805.                           $e->{name},
  2806.               $e->{name2}
  2807.                         );
  2808.         } else {
  2809.              add_to_tree( $e->{pid},
  2810.                           "path",
  2811.                           $profile,
  2812.                           $hat,
  2813.                           $prog,
  2814.                           $sdmode,
  2815.                           $e->{denied_mask},
  2816.                           $e->{name},
  2817.               ""
  2818.                         );
  2819.         }
  2820.     } elsif ($e->{operation} eq "sysctl") {
  2821.         add_to_tree( $e->{pid},
  2822.                      "path",
  2823.                      $profile,
  2824.                      $hat,
  2825.                      $prog,
  2826.                      $sdmode,
  2827.                      $e->{denied_mask},
  2828.                      $e->{name},
  2829.              ""
  2830.                    );
  2831.     } elsif ($e->{operation} eq "clone") {
  2832.         my ($parent, $child)  = ($e->{pid}, $e->{task});
  2833.         $profile ||= "null-complain-profile";
  2834.         $hat     ||= "null-complain-profile";
  2835.         my $arrayref = [];
  2836.         if (exists $pid{$e->{pid}}) {
  2837.             push @{ $pid{$parent} }, $arrayref;
  2838.         } else {
  2839.             push @log, $arrayref;
  2840.         }
  2841.         $pid{$child} = $arrayref;
  2842.         push @{$arrayref}, [ "fork", $child, $profile, $hat ];
  2843.     } elsif ($e->{operation} =~ m/socket_/) {
  2844.         add_to_tree( $e->{pid},
  2845.                      "netdomain",
  2846.                      $profile,
  2847.                      $hat,
  2848.                      $prog,
  2849.                      $sdmode,
  2850.                      $e->{family},
  2851.                      $e->{sock_type},
  2852.                      $e->{protocol},
  2853.                    );
  2854.     } elsif ($e->{operation} eq "change_hat") {
  2855.         add_to_tree($e->{pid}, "unknown_hat", $profile, $hat, $sdmode, $hat);
  2856.     } else {
  2857.         if ( $DEBUGGING ) {
  2858.             my $msg = Data::Dumper->Dump([$e], [qw(*event)]);
  2859.             debug "UNHANDLED: $msg";
  2860.         }
  2861.     }
  2862. }
  2863.  
  2864. sub read_log {
  2865.     $logmark = shift;
  2866.     $seenmark = $logmark ? 0 : 1;
  2867.     my $last;
  2868.     my $event_type;
  2869.  
  2870.     # okay, done loading the previous profiles, get on to the good stuff...
  2871.     open($LOG, $filename)
  2872.       or fatal_error "Can't read AppArmor logfile $filename: $!";
  2873.     while ($_ = get_next_log_entry()) {
  2874.         chomp;
  2875.  
  2876.         $seenmark = 1 if /$logmark/;
  2877.  
  2878.         next unless $seenmark;
  2879.  
  2880.         my $last_match = ""; # v_2_0 syslog record parsing requires
  2881.                              # the previous aa record in the mandatory profile
  2882.                              # case
  2883.         # all we care about is apparmor messages
  2884.         if (/$RE_LOG_v2_0_syslog/ || /$RE_LOG_v2_0_audit/) {
  2885.            $last_match = parse_log_record_v_2_0( $_, $last_match );
  2886.         } else {
  2887.             my $event = parse_log_record($_);
  2888.             add_event_to_tree($event) if ( $event );
  2889.         }
  2890.     }
  2891.     close($LOG);
  2892.     $logmark = "";
  2893. }
  2894.  
  2895.  
  2896. sub UI_SelectUpdatedRepoProfile ($$) {
  2897.  
  2898.     my ($profile, $p) = @_;
  2899.     my $distro        = $cfg->{repository}{distro};
  2900.     my $url           = $sd{$profile}{$profile}{repo}{url};
  2901.     my $user          = $sd{$profile}{$profile}{repo}{user};
  2902.     my $id            = $sd{$profile}{$profile}{repo}{id};
  2903.     my $updated       = 0;
  2904.  
  2905.     if ($p) {
  2906.         my $q = { };
  2907.         $q->{headers} = [
  2908.           "Profile", $profile,
  2909.           "User", $user,
  2910.           "Old Revision", $id,
  2911.           "New Revision", $p->{id},
  2912.         ];
  2913.         $q->{explanation} =
  2914.           gettext( "An updated version of this profile has been found in the profile repository.  Would you like to use it?");
  2915.         $q->{functions} = [
  2916.           "CMD_VIEW_CHANGES", "CMD_UPDATE_PROFILE", "CMD_IGNORE_UPDATE",
  2917.           "CMD_ABORT", "CMD_FINISHED"
  2918.         ];
  2919.  
  2920.         my $ans;
  2921.         do {
  2922.             $ans = UI_PromptUser($q);
  2923.  
  2924.             if ($ans eq "CMD_VIEW_CHANGES") {
  2925.                 my $oldprofile = serialize_profile($sd{$profile}, $profile);
  2926.                 my $newprofile = $p->{profile};
  2927.                 display_changes($oldprofile, $newprofile);
  2928.             }
  2929.         } until $ans =~ /^CMD_(UPDATE_PROFILE|IGNORE_UPDATE)/;
  2930.  
  2931.         if ($ans eq "CMD_UPDATE_PROFILE") {
  2932.             eval {
  2933.                 my $profile_data =
  2934.                   parse_profile_data($p->{profile}, getprofilefilename($profile), 0);
  2935.                 if ($profile_data) {
  2936.                     attach_profile_data(\%sd, $profile_data);
  2937.                     $changed{$profile} = 1;
  2938.                 }
  2939.  
  2940.                 set_repo_info($sd{$profile}{$profile}, $url, $user, $p->{id});
  2941.  
  2942.                 UI_Info(
  2943.                     sprintf(
  2944.                         gettext("Updated profile %s to revision %s."),
  2945.                         $profile, $p->{id}
  2946.                     )
  2947.                 );
  2948.             };
  2949.  
  2950.             if ($@) {
  2951.                 UI_Info(gettext("Error parsing repository profile."));
  2952.             } else {
  2953.                 $updated = 1;
  2954.             }
  2955.         }
  2956.     }
  2957.     return $updated;
  2958. }
  2959.  
  2960. sub UI_repo_signup {
  2961.  
  2962.     my ($url, $res, $save_config, $newuser, $user, $pass, $email, $signup_okay);
  2963.     $url = $cfg->{repository}{url};
  2964.     do {
  2965.         if ($UI_Mode eq "yast") {
  2966.             SendDataToYast(
  2967.                 {
  2968.                     type     => "dialog-repo-sign-in",
  2969.                     repo_url => $url
  2970.                 }
  2971.             );
  2972.             my ($ypath, $yarg) = GetDataFromYast();
  2973.             $email       = $yarg->{email};
  2974.             $user        = $yarg->{user};
  2975.             $pass        = $yarg->{pass};
  2976.             $newuser     = $yarg->{newuser};
  2977.             $save_config = $yarg->{save_config};
  2978.             if ($yarg->{cancelled} && $yarg->{cancelled} eq "y") {
  2979.                 return;
  2980.             }
  2981.             $DEBUGGING && debug("AppArmor Repository: \n\t " .
  2982.                                 ($newuser eq "1") ?
  2983.                                 "New User\n\temail: [" . $email . "]" :
  2984.                                 "Signin" . "\n\t user[" . $user . "]" .
  2985.                                 "password [" . $pass . "]\n");
  2986.         } else {
  2987.             $newuser = UI_YesNo(gettext("Create New User?"), "n");
  2988.             $user    = UI_GetString(gettext("Username: "), $user);
  2989.             $pass    = UI_GetString(gettext("Password: "), $pass);
  2990.             $email   = UI_GetString(gettext("Email Addr: "), $email)
  2991.                          if ($newuser eq "y");
  2992.             $save_config = UI_YesNo(gettext("Save Configuration? "), "y");
  2993.         }
  2994.  
  2995.         if ($newuser eq "y") {
  2996.             my ($status_ok,$res) = user_register($url, $user, $pass, $email);
  2997.             if ($status_ok) {
  2998.                 $signup_okay = 1;
  2999.             } else {
  3000.                 my $errmsg =
  3001.                    gettext("The Profile Repository server returned the following error:") .
  3002.                    "\n" .  $res?$res:gettext("UNKOWN ERROR") .  "\n" .
  3003.                    gettext("Please re-enter registration information or contact the administrator.");
  3004.                 UI_Important(gettext("Login Error\n") . $errmsg);
  3005.             }
  3006.         } else {
  3007.             my ($status_ok,$res) = user_login($url, $user, $pass);
  3008.             if ($status_ok) {
  3009.                 $signup_okay = 1;
  3010.             } else {
  3011.                 my $errmsg = gettext("Login failure\n Please check username and password and try again.") . "\n" . $res;
  3012.                 UI_Important($errmsg);
  3013.             }
  3014.         }
  3015.     } until $signup_okay;
  3016.  
  3017.     $repo_cfg->{repository}{user} = $user;
  3018.     $repo_cfg->{repository}{pass} = $pass;
  3019.     $repo_cfg->{repository}{email} = $email;
  3020.  
  3021.     write_config("repository.conf", $repo_cfg) if ( $save_config eq "y" );
  3022.  
  3023.     return ($user, $pass);
  3024. }
  3025.  
  3026. sub UI_ask_to_enable_repo {
  3027.  
  3028.     my $q = { };
  3029.     return if ( not defined $cfg->{repository}{url} );
  3030.     $q->{headers} = [
  3031.       "Repository", $cfg->{repository}{url},
  3032.     ];
  3033.     $q->{explanation} = gettext( "Would you like to enable access to the
  3034. profile repository?" ); $q->{functions} = [ "CMD_ENABLE_REPO",
  3035. "CMD_DISABLE_REPO", "CMD_ASK_LATER", ];
  3036.  
  3037.     my $cmd;
  3038.     do {
  3039.         $cmd = UI_PromptUser($q);
  3040.     } until $cmd =~ /^CMD_(ENABLE_REPO|DISABLE_REPO|ASK_LATER)/;
  3041.  
  3042.     if ($cmd eq "CMD_ENABLE_REPO") {
  3043.         $repo_cfg->{repository}{enabled} = "yes";
  3044.     } elsif ($cmd eq "CMD_DISABLE_REPO") {
  3045.         $repo_cfg->{repository}{enabled} = "no";
  3046.     } elsif ($cmd eq "CMD_ASK_LATER") {
  3047.         $repo_cfg->{repository}{enabled} = "later";
  3048.     }
  3049.  
  3050.     eval { write_config("repository.conf", $repo_cfg) };
  3051.     if ($@) {
  3052.         fatal_error($@);
  3053.     }
  3054. }
  3055.  
  3056.  
  3057. sub UI_ask_to_upload_profiles {
  3058.  
  3059.     my $q = { };
  3060.     $q->{headers} = [
  3061.       "Repository", $cfg->{repository}{url},
  3062.     ];
  3063.     $q->{explanation} =
  3064.       gettext( "Would you like to upload newly created and changed profiles to
  3065.       the profile repository?" );
  3066.     $q->{functions} = [
  3067.       "CMD_YES", "CMD_NO", "CMD_ASK_LATER",
  3068.     ];
  3069.  
  3070.     my $cmd;
  3071.     do {
  3072.         $cmd = UI_PromptUser($q);
  3073.     } until $cmd =~ /^CMD_(YES|NO|ASK_LATER)/;
  3074.  
  3075.     if ($cmd eq "CMD_NO") {
  3076.         $repo_cfg->{repository}{upload} = "no";
  3077.     } elsif ($cmd eq "CMD_YES") {
  3078.         $repo_cfg->{repository}{upload} = "yes";
  3079.     } elsif ($cmd eq "CMD_ASK_LATER") {
  3080.         $repo_cfg->{repository}{upload} = "later";
  3081.     }
  3082.  
  3083.     eval { write_config("repository.conf", $repo_cfg) };
  3084.     if ($@) {
  3085.         fatal_error($@);
  3086.     }
  3087. }
  3088.  
  3089.  
  3090. sub parse_repo_profile {
  3091.     my ($fqdbin, $repo_url, $profile) = @_;
  3092.  
  3093.     my $profile_data = eval {
  3094.         parse_profile_data($profile->{profile}, getprofilefilename($fqdbin), 0);
  3095.     };
  3096.     if ($@) {
  3097.         print STDERR "PARSING ERROR: $@\n";
  3098.         $profile_data = undef;
  3099.     }
  3100.  
  3101.     if ($profile_data) {
  3102.         set_repo_info($profile_data->{$fqdbin}{$fqdbin}, $repo_url,
  3103.                       $profile->{username}, $profile->{id});
  3104.     }
  3105.  
  3106.     return $profile_data;
  3107. }
  3108.  
  3109.  
  3110. sub set_repo_info {
  3111.     my ($profile_data, $repo_url, $username, $id) = @_;
  3112.  
  3113.     # save repository metadata
  3114.     $profile_data->{repo}{url}  = $repo_url;
  3115.     $profile_data->{repo}{user} = $username;
  3116.     $profile_data->{repo}{id}   = $id;
  3117. }
  3118.  
  3119.  
  3120. sub is_repo_profile {
  3121.     my $profile_data = shift;
  3122.  
  3123.     return $profile_data->{repo}{url}  &&
  3124.            $profile_data->{repo}{user} &&
  3125.            $profile_data->{repo}{id};
  3126. }
  3127.  
  3128.  
  3129. sub get_repo_user_pass {
  3130.     my ($user, $pass);
  3131.  
  3132.     if ($repo_cfg) {
  3133.         $user = $repo_cfg->{repository}{user};
  3134.         $pass = $repo_cfg->{repository}{pass};
  3135.     }
  3136.  
  3137.     unless ($user && $pass) {
  3138.         ($user, $pass) = UI_repo_signup();
  3139.     }
  3140.  
  3141.     return ($user, $pass);
  3142. }
  3143.  
  3144.  
  3145. sub get_preferred_user ($) {
  3146.     my $repo_url = shift;
  3147.     return $cfg->{repository}{preferred_user} || "NOVELL";
  3148. }
  3149.  
  3150.  
  3151. sub repo_is_enabled () {
  3152.     my $enabled;
  3153.     if ($cfg->{repository}{url} &&
  3154.         $repo_cfg &&
  3155.         $repo_cfg->{repository}{enabled} &&
  3156.         $repo_cfg->{repository}{enabled} eq "yes") {
  3157.         $enabled = 1;
  3158.     }
  3159.     return $enabled;
  3160. }
  3161.  
  3162.  
  3163. sub update_repo_profile {
  3164.     my $profile = shift;
  3165.  
  3166.     return undef if ( not is_repo_profile($profile) );
  3167.     my $distro = $cfg->{repository}{distro};
  3168.     my $url    = $profile->{repo}{url};
  3169.     my $user   = $profile->{repo}{user};
  3170.     my $id     = $profile->{repo}{id};
  3171.  
  3172.     UI_BusyStart( gettext("Connecting to repository.....") );
  3173.     my ($status_ok,$res) = fetch_newer_profile( $url,
  3174.                                                 $distro,
  3175.                                                 $user,
  3176.                                                 $id,
  3177.                                                 $profile->{name}
  3178.                                               );
  3179.     UI_BusyStop();
  3180.     if ( ! $status_ok ) {
  3181.         my $errmsg =
  3182.           sprintf(
  3183.             gettext("WARNING: Profile update check failed\nError Detail:\n%s"),
  3184.             defined $res?$res:gettext("UNKNOWN ERROR"));
  3185.         UI_Important($errmsg);
  3186.         $res = undef;
  3187.     }
  3188.     return( $res );
  3189. }
  3190.  
  3191. sub UI_ask_mode_toggles ($$$) {
  3192.     my ($audit_toggle, $owner_toggle, $oldmode) = @_;
  3193.     my $q = { };
  3194.     $q->{headers} = [ ];
  3195. #      "Repository", $cfg->{repository}{url},
  3196. #    ];
  3197.     $q->{explanation} = gettext( "Change mode modifiers");
  3198.  
  3199.     if ($audit_toggle) {
  3200.     $q->{functions} = [ "CMD_AUDIT_OFF" ];
  3201.     } else {
  3202.     $q->{functions} = [ "CMD_AUDIT_NEW" ];
  3203.     push @{$q->{functions}}, "CMD_AUDIT_FULL" if ($oldmode);
  3204.     }
  3205.  
  3206.     if ($owner_toggle) {
  3207.     push @{$q->{functions}}, "CMD_USER_OFF";
  3208.     } else {
  3209.     push @{$q->{functions}}, "CMD_USER_ON";
  3210.     }
  3211.     push @{$q->{functions}}, "CMD_CONTINUE";
  3212.  
  3213.     my $cmd;
  3214.     do {
  3215.         $cmd = UI_PromptUser($q);
  3216.     } until $cmd =~ /^CMD_(AUDIT_OFF|AUDIT_NEW|AUDIT_FULL|USER_ON|USER_OFF|CONTINUE)/;
  3217.  
  3218.     if ($cmd eq "CMD_AUDIT_OFF") {
  3219.     $audit_toggle = 0;
  3220.     } elsif ($cmd eq "CMD_AUDIT_NEW") {
  3221.     $audit_toggle = 1;
  3222.     } elsif ($cmd eq "CMD_AUDIT_FULL") {
  3223.     $audit_toggle = 2;
  3224.     } elsif ($cmd eq "CMD_USER_ON") {
  3225.     $owner_toggle = 1;
  3226.     } elsif ($cmd eq "CMD_USER_OFF") {
  3227.     $owner_toggle = 0;
  3228. #    $owner_toggle++;
  3229. #    $owner_toggle++ if (!$oldmode && $owner_toggle == 2);
  3230. #    $owner_toggle = 0 if ($owner_toggle > 3);
  3231.     }
  3232.     return ($audit_toggle, $owner_toggle);
  3233. }
  3234.  
  3235. sub ask_the_questions {
  3236.     my $found; # do the magic foo-foo
  3237.     for my $sdmode (sort keys %log) {
  3238.  
  3239.         # let them know what sort of changes we're about to list...
  3240.         if ($sdmode eq "PERMITTING") {
  3241.             UI_Info(gettext("Complain-mode changes:"));
  3242.         } elsif ($sdmode eq "REJECTING") {
  3243.             UI_Info(gettext("Enforce-mode changes:"));
  3244.         } else {
  3245.  
  3246.             # if we're not permitting and not rejecting, something's broken.
  3247.             # most likely  the code we're using to build the hash tree of log
  3248.             # entries - this should never ever happen
  3249.             fatal_error(sprintf(gettext('Invalid mode found: %s'), $sdmode));
  3250.         }
  3251.  
  3252.         for my $profile (sort keys %{ $log{$sdmode} }) {
  3253.             my $p = update_repo_profile($sd{$profile}{$profile});
  3254.             UI_SelectUpdatedRepoProfile($profile, $p) if ( $p );
  3255.  
  3256.             $found++;
  3257.  
  3258.             # this sorts the list of hats, but makes sure that the containing
  3259.             # profile shows up in the list first to keep the question order
  3260.             # rational
  3261.             my @hats =
  3262.               grep { $_ ne $profile } keys %{ $log{$sdmode}{$profile} };
  3263.             unshift @hats, $profile
  3264.               if defined $log{$sdmode}{$profile}{$profile};
  3265.  
  3266.             for my $hat (@hats) {
  3267.  
  3268.                 # step through all the capabilities first...
  3269.                 for my $capability (sort keys %{ $log{$sdmode}{$profile}{$hat}{capability} }) {
  3270.  
  3271.                     # we don't care about it if we've already added it to the
  3272.                     # profile
  3273.                     next if profile_known_capability($sd{$profile}{$hat},
  3274.                              $capability);
  3275.  
  3276.                     my $severity = $sevdb->rank(uc("cap_$capability"));
  3277.  
  3278.                     my $defaultoption = 1;
  3279.                     my @options       = ();
  3280.                     my @newincludes;
  3281.                     @newincludes = matchcapincludes($sd{$profile}{$hat},
  3282.                                                     $capability);
  3283.  
  3284.  
  3285.                     my $q = {};
  3286.  
  3287.                     if (@newincludes) {
  3288.                         push @options,
  3289.                           map { "#include <$_>" } sort(uniq(@newincludes));
  3290.                     }
  3291.  
  3292.                     if ( @options ) {
  3293.                         push @options, "capability $capability";
  3294.                         $q->{options}  = [@options];
  3295.                         $q->{selected} = $defaultoption - 1;
  3296.                     }
  3297.  
  3298.                     $q->{headers} = [];
  3299.                     push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  3300.                     push @{ $q->{headers} }, gettext("Capability"), $capability;
  3301.                     push @{ $q->{headers} }, gettext("Severity"),   $severity;
  3302.  
  3303.             my $audit_toggle = 0;
  3304.             $q->{functions} = [
  3305.             "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_NEW", "CMD_ABORT", "CMD_FINISHED"
  3306.             ];
  3307.  
  3308.                     # complain-mode events default to allow - enforce defaults
  3309.                     # to deny
  3310.                     $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ALLOW" : "CMD_DENY";
  3311.  
  3312.                     $seenevents++;
  3313.                     my $done = 0;
  3314.                     while ( not $done ) {
  3315.                         # what did the grand exalted master tell us to do?
  3316.                         my ($ans, $selected) = UI_PromptUser($q);
  3317.  
  3318.             if ($ans =~ /^CMD_AUDIT/) {
  3319.                 $audit_toggle = !$audit_toggle;
  3320.                 my $audit = "";
  3321.                 if ($audit_toggle) {
  3322.                 $q->{functions} = [
  3323.                     "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_OFF", "CMD_ABORT", "CMD_FINISHED"
  3324.                     ];
  3325.                 $audit = "audit ";
  3326.                 } else {
  3327.                 $q->{functions} = [
  3328.                     "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_NEW", "CMD_ABORT", "CMD_FINISHED"
  3329.                     ];
  3330.                 }
  3331.                 $q->{headers} = [];
  3332.                 push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  3333.                 push @{ $q->{headers} }, gettext("Capability"), $audit . $capability;
  3334.                 push @{ $q->{headers} }, gettext("Severity"),   $severity;
  3335.  
  3336.                         } if ($ans eq "CMD_ALLOW") {
  3337.  
  3338.                             # they picked (a)llow, so...
  3339.  
  3340.                             my $selection = $options[$selected];
  3341.                             $done = 1;
  3342.                             if ($selection &&
  3343.                                 $selection =~ m/^#include <(.+)>$/) {
  3344.                                 my $deleted = 0;
  3345.                                 my $inc = $1;
  3346.                                 $deleted = delete_duplicates($sd{$profile}{$hat},
  3347.                                                                $inc
  3348.                                                              );
  3349.                                 $sd{$profile}{$hat}{include}{$inc} = 1;
  3350.  
  3351.                                 $changed{$profile} = 1;
  3352.                                 UI_Info(sprintf(
  3353.                                   gettext('Adding #include <%s> to profile.'),
  3354.                                           $inc));
  3355.                                 UI_Info(sprintf(
  3356.                                   gettext('Deleted %s previous matching profile entries.'),
  3357.                                            $deleted)) if $deleted;
  3358.                             }
  3359.                             # stick the capability into the profile
  3360.                             $sd{$profile}{$hat}{allow}{capability}{$capability}{set} = 1;
  3361.                             $sd{$profile}{$hat}{allow}{capability}{$capability}{audit} = $audit_toggle;
  3362.  
  3363.                             # mark this profile as changed
  3364.                             $changed{$profile} = 1;
  3365.                             $done = 1;
  3366.                             # give a little feedback to the user
  3367.                             UI_Info(sprintf(gettext('Adding capability %s to profile.'), $capability));
  3368.                         } elsif ($ans eq "CMD_DENY") {
  3369.                             $sd{$profile}{$hat}{deny}{capability}{$capability}{set} = 1;
  3370.                             # mark this profile as changed
  3371.                             $changed{$profile} = 1;
  3372.                             UI_Info(sprintf(gettext('Denying capability %s to profile.'), $capability));
  3373.                             $done = 1;
  3374.                         } else {
  3375.                             redo;
  3376.                         }
  3377.                     }
  3378.                 }
  3379.  
  3380.                 # and then step through all of the path entries...
  3381.                 for my $path (sort keys %{ $log{$sdmode}{$profile}{$hat}{path} }) {
  3382.  
  3383.                     my $mode = $log{$sdmode}{$profile}{$hat}{path}{$path};
  3384.  
  3385.             # do original profile lookup once.
  3386.  
  3387.             my $allow_mode = 0;
  3388.             my $allow_audit = 0;
  3389.             my $deny_mode = 0;
  3390.             my $deny_audit = 0;
  3391.  
  3392.             my ($fmode, $famode, $imode, $iamode, @fm, @im, $cm, $am, $cam, @m);
  3393.             ($fmode, $famode, @fm) = rematchfrag($sd{$profile}{$hat}, 'allow', $path);
  3394.             $allow_mode |= $fmode if $fmode;
  3395.             $allow_audit |= $famode if $famode;
  3396.             ($imode, $iamode, @im) = match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $path);
  3397.             $allow_mode |= $imode if $imode;
  3398.             $allow_audit |= $iamode if $iamode;
  3399.  
  3400.             ($cm, $cam, @m) = rematchfrag($sd{$profile}{$hat}, 'deny', $path);
  3401.             $deny_mode |= $cm if $cm;
  3402.             $deny_audit |= $cam if $cam;
  3403.             ($cm, $cam, @m) = match_prof_incs_to_path($sd{$profile}{$hat}, 'deny', $path);
  3404.             $deny_mode |= $cm if $cm;
  3405.             $deny_audit |= $cam if $cam;
  3406.  
  3407.             if ($deny_mode & $AA_MAY_EXEC) {
  3408.             $deny_mode |= $ALL_AA_EXEC_TYPE;
  3409.             }
  3410.  
  3411.             # mask off the modes that have been denied
  3412.             $mode &= ~$deny_mode;
  3413.             $allow_mode &= ~$deny_mode;
  3414.  
  3415.                     # if we had an access(X_OK) request or some other kind of
  3416.                     # event that generates a "PERMITTING x" syslog entry,
  3417.                     # first check if it was already dealt with by a i/p/x
  3418.                     # question due to a exec().  if not, ask about adding ix
  3419.                     # permission.
  3420.                     if ($mode & $AA_MAY_EXEC) {
  3421.  
  3422.                         # get rid of the access() markers.
  3423.                         $mode &= (~$ALL_AA_EXEC_TYPE);
  3424.  
  3425.                         unless ($allow_mode & $allow_mode & $AA_MAY_EXEC) {
  3426.                             $mode |= str_to_mode("ix");
  3427.                         }
  3428.                     }
  3429.  
  3430.                     # if we had an mmap(PROT_EXEC) request, first check if we
  3431.                     # already have added an ix rule to the profile
  3432.                     if ($mode & $AA_EXEC_MMAP) {
  3433.                         # ix implies m.  don't ask if they want to add an "m"
  3434.                         # rule when we already have a matching ix rule.
  3435.                         if ($allow_mode && contains($allow_mode, "ix")) {
  3436.                             $mode &= (~$AA_EXEC_MMAP);
  3437.                         }
  3438.                     }
  3439.  
  3440.                     next unless $mode;
  3441.  
  3442.  
  3443.                     my @matches;
  3444.  
  3445.                     if ($fmode) {
  3446.                         push @matches, @fm;
  3447.                     }
  3448.                     if ($imode) {
  3449.                         push @matches, @im;
  3450.                     }
  3451.  
  3452.                     unless ($allow_mode && mode_contains($allow_mode, $mode)) {
  3453.  
  3454.                         my $defaultoption = 1;
  3455.                         my @options       = ();
  3456.  
  3457.                         # check the path against the available set of include
  3458.                         # files
  3459.                         my @newincludes;
  3460.                         my $includevalid;
  3461.                         for my $incname (keys %include) {
  3462.                             $includevalid = 0;
  3463.  
  3464.                             # don't suggest it if we're already including it,
  3465.                             # that's dumb
  3466.                             next if $sd{$profile}{$hat}{$incname};
  3467.  
  3468.                             # only match includes that can be suggested to
  3469.                             # the user
  3470.                 if ($cfg->{settings}{custom_includes}) {
  3471.                             for my $incm (split(/\s+/,
  3472.                                                 $cfg->{settings}{custom_includes})
  3473.                                          ) {
  3474.                                 $includevalid = 1 if $incname =~ /$incm/;
  3475.                             }
  3476.                 }
  3477.                             $includevalid = 1 if $incname =~ /abstractions/;
  3478.                             next if ($includevalid == 0);
  3479.  
  3480.                             ($cm, $am, @m) = match_include_to_path($incname, 'allow', $path);
  3481.                             if ($cm && mode_contains($cm, $mode)) {
  3482.                 #make sure it doesn't deny $mode
  3483.                 my $dm = match_include_to_path($incname, 'deny', $path);
  3484.                 unless (($mode & $dm) || (grep { $_ eq "/**" } @m)) {
  3485.                                     push @newincludes, $incname;
  3486.                                 }
  3487.                             }
  3488.                         }
  3489.  
  3490.  
  3491.                         # did any match?  add them to the option list...
  3492.                         if (@newincludes) {
  3493.                             push @options,
  3494.                               map { "#include <$_>" }
  3495.                               sort(uniq(@newincludes));
  3496.                         }
  3497.  
  3498.                         # include the literal path in the option list...
  3499.                         push @options, $path;
  3500.  
  3501.                         # match the current path against the globbing list in
  3502.                         # logprof.conf
  3503.                         my @globs = globcommon($path);
  3504.                         if (@globs) {
  3505.                             push @matches, @globs;
  3506.                         }
  3507.  
  3508.                         # suggest any matching globs the user manually entered
  3509.                         for my $userglob (@userglobs) {
  3510.                             push @matches, $userglob
  3511.                               if matchliteral($userglob, $path);
  3512.                         }
  3513.  
  3514.                         # we'll take the cheesy way and order the suggested
  3515.                         # globbing list by length, which is usually right,
  3516.                         # but not always always
  3517.                         push @options,
  3518.                           sort { length($b) <=> length($a) }
  3519.                           grep { $_ ne $path }
  3520.                           uniq(@matches);
  3521.                         $defaultoption = $#options + 1;
  3522.  
  3523.                         my $severity = $sevdb->rank($path, mode_to_str($mode));
  3524.  
  3525.             my $audit_toggle = 0;
  3526.             my $owner_toggle = $cfg->{settings}{default_owner_prompt};
  3527.                         my $done = 0;
  3528.                         while (not $done) {
  3529.  
  3530.                             my $q = {};
  3531.                             $q->{headers} = [];
  3532.                             push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  3533.                             push @{ $q->{headers} }, gettext("Path"), $path;
  3534.  
  3535.                             # merge in any previous modes from this run
  3536.                             if ($allow_mode) {
  3537.                 my $str;
  3538. #print "mode: " . print_mode($mode) . " allow: " . print_mode($allow_mode) . "\n";
  3539.                                 $mode |= $allow_mode;
  3540.                 my $tail;
  3541.                 my $prompt_mode;
  3542.                 if ($owner_toggle == 0) {
  3543.                     $prompt_mode = flatten_mode($mode);
  3544.                     $tail = "     " . gettext("(owner permissions off)");
  3545.                 } elsif ($owner_toggle == 1) {
  3546.                     $prompt_mode = $mode;
  3547.                     $tail = "";
  3548.                 } elsif ($owner_toggle == 2) {
  3549.                     $prompt_mode = $allow_mode | owner_flatten_mode($mode & ~$allow_mode);
  3550.                     $tail = "     " . gettext("(force new perms to owner)");
  3551.                 } else {
  3552.                     $prompt_mode = owner_flatten_mode($mode);
  3553.                     $tail = "     " . gettext("(force all rule perms to owner)");
  3554.                 }
  3555.  
  3556.                 if ($audit_toggle == 1) {
  3557.                     $str = mode_to_str_user($allow_mode);
  3558.                     $str .= ", " if ($allow_mode);
  3559.                     $str .= "audit " . mode_to_str_user($prompt_mode & ~$allow_mode) . $tail;
  3560.                 } elsif ($audit_toggle == 2) {
  3561.                     $str = "audit " . mode_to_str_user($prompt_mode) . $tail;
  3562.                 } else {
  3563.                     $str = mode_to_str_user($prompt_mode) . $tail;
  3564.                 }
  3565.                                 push @{ $q->{headers} }, gettext("Old Mode"), mode_to_str_user($allow_mode);
  3566.                                 push @{ $q->{headers} }, gettext("New Mode"), $str;
  3567.                             } else {
  3568.                 my $str = "";
  3569.                 if ($audit_toggle) {
  3570.                     $str = "audit ";
  3571.                 }
  3572.                 my $tail;
  3573.                 my $prompt_mode;
  3574.                 if ($owner_toggle == 0) {
  3575.                     $prompt_mode = flatten_mode($mode);
  3576.                     $tail = "     " . gettext("(owner permissions off)");
  3577.                 } elsif ($owner_toggle == 1) {
  3578.                     $prompt_mode = $mode;
  3579.                     $tail = "";
  3580.                 } else {
  3581.                     $prompt_mode = owner_flatten_mode($mode);
  3582.                     $tail = "     " . gettext("(force perms to owner)");
  3583.                 }
  3584.                 $str .= mode_to_str_user($prompt_mode) . $tail;
  3585.                                 push @{ $q->{headers} }, gettext("Mode"), $str; 
  3586.                             }
  3587.                             push @{ $q->{headers} }, gettext("Severity"), $severity;
  3588.  
  3589.                             $q->{options}  = [@options];
  3590.                             $q->{selected} = $defaultoption - 1;
  3591.  
  3592.                             $q->{functions} = [
  3593.                               "CMD_ALLOW", "CMD_DENY", "CMD_GLOB", "CMD_GLOBEXT", "CMD_NEW",
  3594.                               "CMD_ABORT", "CMD_FINISHED", "CMD_OTHER"
  3595.                             ];
  3596.  
  3597.                             $q->{default} =
  3598.                               ($sdmode eq "PERMITTING")
  3599.                               ? "CMD_ALLOW"
  3600.                               : "CMD_DENY";
  3601.  
  3602.                             $seenevents++;
  3603.                             # if they just hit return, use the default answer
  3604.                             my ($ans, $selected) = UI_PromptUser($q);
  3605.  
  3606.                 if ($ans eq "CMD_OTHER") {
  3607.  
  3608.                 ($audit_toggle, $owner_toggle) = UI_ask_mode_toggles($audit_toggle, $owner_toggle, $allow_mode);
  3609.                 } elsif ($ans eq "CMD_USER_TOGGLE") {
  3610.                 $owner_toggle++;
  3611.                 $owner_toggle++ if (!$allow_mode && $owner_toggle == 2);
  3612.                 $owner_toggle = 0 if ($owner_toggle > 3);
  3613.                 } elsif ($ans eq "CMD_ALLOW") {
  3614.                                 $path = $options[$selected];
  3615.                                 $done = 1;
  3616.                                 if ($path =~ m/^#include <(.+)>$/) {
  3617.                                     my $inc = $1;
  3618.                                     my $deleted = 0;
  3619.  
  3620.                                     $deleted = delete_duplicates($sd{$profile}{$hat},
  3621.                                                                   $inc );
  3622.  
  3623.                                     # record the new entry
  3624.                                     $sd{$profile}{$hat}{include}{$inc} = 1;
  3625.  
  3626.                                     $changed{$profile} = 1;
  3627.                                     UI_Info(sprintf(gettext('Adding #include <%s> to profile.'), $inc));
  3628.                                     UI_Info(sprintf(gettext('Deleted %s previous matching profile entries.'), $deleted)) if $deleted;
  3629.                                 } else {
  3630.                                     if ($sd{$profile}{$hat}{allow}{path}{$path}{mode}) {
  3631.                                         $mode = $mode | $sd{$profile}{$hat}{allow}{path}{$path}{mode};
  3632.                                     }
  3633.  
  3634.                                     my $deleted = 0;
  3635.                                     for my $entry (keys %{ $sd{$profile}{$hat}{allow}{path} }) {
  3636.  
  3637.                                         next if $path eq $entry;
  3638.  
  3639.                                         if (matchregexp($path, $entry)) {
  3640.  
  3641.                                             # regexp matches, add it's mode to
  3642.                                             # the list to check against
  3643.                                             if (mode_contains($mode,
  3644.                                                 $sd{$profile}{$hat}{allow}{path}{$entry}{mode})) {
  3645.                                                 delete $sd{$profile}{$hat}{allow}{path}{$entry};
  3646.                                                 $deleted++;
  3647.                                             }
  3648.                                         }
  3649.                                     }
  3650.  
  3651.                                     # record the new entry
  3652.                     if ($owner_toggle == 0) {
  3653.                     $mode = flatten_mode($mode);
  3654.                     } elsif ($owner_toggle == 1) {
  3655.                     $mode = $mode;
  3656.                     } elsif ($owner_toggle == 2) {
  3657.                     $mode = $allow_mode | owner_flatten_mode($mode & ~$allow_mode);
  3658.                     } elsif  ($owner_toggle == 3) {
  3659.                     $mode = owner_flatten_mode($mode);
  3660.                     }
  3661.                                     $sd{$profile}{$hat}{allow}{path}{$path}{mode} = $mode;
  3662.                     my $tmpmode = ($audit_toggle == 1) ? $mode & ~$allow_mode : 0;
  3663.                     $tmpmode = ($audit_toggle == 2) ? $mode : $tmpmode;
  3664.                                     $sd{$profile}{$hat}{allow}{path}{$path}{audit} |= $tmpmode;
  3665.  
  3666.                                     $changed{$profile} = 1;
  3667.                                     UI_Info(sprintf(gettext('Adding %s %s to profile.'), $path, mode_to_str_user($mode)));
  3668.                                     UI_Info(sprintf(gettext('Deleted %s previous matching profile entries.'), $deleted)) if $deleted;
  3669.                                 }
  3670.                             } elsif ($ans eq "CMD_DENY") {
  3671.                 # record the new entry
  3672.                 $sd{$profile}{$hat}{deny}{path}{$path}{mode} |= $mode & ~$allow_mode;
  3673.                 $sd{$profile}{$hat}{deny}{path}{$path}{audit} |= 0;
  3674.  
  3675.                 $changed{$profile} = 1;
  3676.  
  3677.                                 # go on to the next entry without saving this
  3678.                                 # one
  3679.                                 $done = 1;
  3680.                             } elsif ($ans eq "CMD_NEW") {
  3681.                                 my $arg = $options[$selected];
  3682.                                 if ($arg !~ /^#include/) {
  3683.                                     $ans = UI_GetString(gettext("Enter new path: "), $arg);
  3684.                                     if ($ans) {
  3685.                                         unless (matchliteral($ans, $path)) {
  3686.                                             my $ynprompt = gettext("The specified path does not match this log entry:") . "\n\n";
  3687.                                             $ynprompt .= "  " . gettext("Log Entry") . ":    $path\n";
  3688.                                             $ynprompt .= "  " . gettext("Entered Path") . ": $ans\n\n";
  3689.                                             $ynprompt .= gettext("Do you really want to use this path?") . "\n";
  3690.  
  3691.                                             # we default to no if they just hit return...
  3692.                                             my $key = UI_YesNo($ynprompt, "n");
  3693.  
  3694.                                             next if $key eq "n";
  3695.                                         }
  3696.  
  3697.                                         # save this one for later
  3698.                                         push @userglobs, $ans;
  3699.  
  3700.                                         push @options, $ans;
  3701.                                         $defaultoption = $#options + 1;
  3702.                                     }
  3703.                                 }
  3704.                             } elsif ($ans eq "CMD_GLOB") {
  3705.  
  3706.                                 # do globbing if they don't have an include
  3707.                                 # selected
  3708.                                 my $newpath = $options[$selected];
  3709.                                 chomp $newpath ;
  3710.                                 unless ($newpath =~ /^#include/) {
  3711.                                     # is this entry directory specific
  3712.                                     if ( $newpath =~ m/\/$/ ) {
  3713.                                         # do we collapse to /* or /**?
  3714.                                         if ($newpath =~ m/\/\*{1,2}\/$/) {
  3715.                                             $newpath =~
  3716.                                             s/\/[^\/]+\/\*{1,2}\/$/\/\*\*\//;
  3717.                                         } else {
  3718.                                             $newpath =~ s/\/[^\/]+\/$/\/\*\//;
  3719.                                         }
  3720.                                     } else {
  3721.                                         # do we collapse to /* or /**?
  3722.                                         if ($newpath =~ m/\/\*{1,2}$/) {
  3723.                                             $newpath =~ s/\/[^\/]+\/\*{1,2}$/\/\*\*/;
  3724.                                         } else {
  3725.                                             $newpath =~ s/\/[^\/]+$/\/\*/;
  3726.                                         }
  3727.                                     }
  3728.                                     if ($newpath ne $selected) {
  3729.                                         push @options, $newpath;
  3730.                                         $defaultoption = $#options + 1;
  3731.                                     }
  3732.                                 }
  3733.                             } elsif ($ans eq "CMD_GLOBEXT") {
  3734.  
  3735.                                 # do globbing if they don't have an include
  3736.                                 # selected
  3737.                                 my $newpath = $options[$selected];
  3738.                                 unless ($newpath =~ /^#include/) {
  3739.                                     # do we collapse to /*.ext or /**.ext?
  3740.                                     if ($newpath =~ m/\/\*{1,2}\.[^\/]+$/) {
  3741.                                         $newpath =~ s/\/[^\/]+\/\*{1,2}(\.[^\/]+)$/\/\*\*$1/;
  3742.                                     } else {
  3743.                                         $newpath =~ s/\/[^\/]+(\.[^\/]+)$/\/\*$1/;
  3744.                                     }
  3745.                                     if ($newpath ne $selected) {
  3746.                                         push @options, $newpath;
  3747.                                         $defaultoption = $#options + 1;
  3748.                                     }
  3749.                                 }
  3750.                             } elsif ($ans =~ /\d/) {
  3751.                                 $defaultoption = $ans;
  3752.                             }
  3753.                         }
  3754.                     }
  3755.                 }
  3756.  
  3757.                 # and then step through all of the netdomain entries...
  3758.                 for my $family (sort keys %{$log{$sdmode}
  3759.                                                 {$profile}
  3760.                                                 {$hat}
  3761.                                                 {netdomain}}) {
  3762.  
  3763.                     # TODO - severity handling for net toggles
  3764.                     #my $severity = $sevdb->rank();
  3765.                     for my $sock_type (sort keys %{$log{$sdmode}
  3766.                                                        {$profile}
  3767.                                                        {$hat}
  3768.                                                        {netdomain}
  3769.                                                        {$family}}) {
  3770.  
  3771.                         # we don't care about it if we've already added it to the
  3772.                         # profile
  3773.                         next if ( profile_known_network($sd{$profile}{$hat},
  3774.                             $family,
  3775.                             $sock_type));
  3776.                         my $defaultoption = 1;
  3777.                         my @options       = ();
  3778.                         my @newincludes;
  3779.                         @newincludes = matchnetincludes($sd{$profile}{$hat},
  3780.                                                         $family,
  3781.                                                         $sock_type);
  3782.  
  3783.                         my $q = {};
  3784.  
  3785.                         if (@newincludes) {
  3786.                             push @options,
  3787.                               map { "#include <$_>" } sort(uniq(@newincludes));
  3788.                         }
  3789.  
  3790.                         if ( @options ) {
  3791.                             push @options, "network $family $sock_type";
  3792.                             $q->{options}  = [@options];
  3793.                             $q->{selected} = $defaultoption - 1;
  3794.                         }
  3795.  
  3796.                         $q->{headers} = [];
  3797.                         push @{ $q->{headers} },
  3798.                              gettext("Profile"),
  3799.                              combine_name($profile, $hat);
  3800.                         push @{ $q->{headers} },
  3801.                              gettext("Network Family"),
  3802.                              $family;
  3803.                         push @{ $q->{headers} },
  3804.                              gettext("Socket Type"),
  3805.                              $sock_type;
  3806.  
  3807.             my $audit_toggle = 0;
  3808.  
  3809.                         $q->{functions} = [
  3810.                                             "CMD_ALLOW",
  3811.                                             "CMD_DENY",
  3812.                         "CMD_AUDIT_NEW",
  3813.                                             "CMD_ABORT",
  3814.                                             "CMD_FINISHED"
  3815.                                           ];
  3816.  
  3817.                         # complain-mode events default to allow - enforce defaults
  3818.                         # to deny
  3819.                         $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ALLOW" :
  3820.                                                                     "CMD_DENY";
  3821.  
  3822.                         $seenevents++;
  3823.  
  3824.                         # what did the grand exalted master tell us to do?
  3825.                         my $done = 0;
  3826.                         while ( not $done ) {
  3827.                             my ($ans, $selected) = UI_PromptUser($q);
  3828.                 if ($ans =~ /^CMD_AUDIT/) {
  3829.                 $audit_toggle = !$audit_toggle;
  3830.                 my $audit = $audit_toggle ? "audit " : "";
  3831.                 if ($audit_toggle) {
  3832.                     $q->{functions} = [
  3833.                     "CMD_ALLOW",
  3834.                     "CMD_DENY",
  3835.                     "CMD_AUDIT_OFF",
  3836.                     "CMD_ABORT",
  3837.                     "CMD_FINISHED"
  3838.                     ];
  3839.                 } else {
  3840.                     $q->{functions} = [
  3841.                     "CMD_ALLOW",
  3842.                     "CMD_DENY",
  3843.                     "CMD_AUDIT_NEW",
  3844.                     "CMD_ABORT",
  3845.                     "CMD_FINISHED"
  3846.                     ];
  3847.                 }
  3848.                 $q->{headers} = [];
  3849.                 push @{ $q->{headers} },
  3850.                 gettext("Profile"),
  3851.                 combine_name($profile, $hat);
  3852.                 push @{ $q->{headers} },
  3853.                 gettext("Network Family"),
  3854.                 $audit . $family;
  3855.                 push @{ $q->{headers} },
  3856.                 gettext("Socket Type"),
  3857.                 $sock_type;
  3858.                             } elsif ($ans eq "CMD_ALLOW") {
  3859.                                 my $selection = $options[$selected];
  3860.                                 $done = 1;
  3861.                                 if ($selection &&
  3862.                                     $selection =~ m/^#include <(.+)>$/) {
  3863.                                     my $inc = $1;
  3864.                                     my $deleted = 0;
  3865.                                     $deleted = delete_duplicates($sd{$profile}{$hat},
  3866.                                                                    $inc
  3867.                                                                  );
  3868.                                     # record the new entry
  3869.                                     $sd{$profile}{$hat}{include}{$inc} = 1;
  3870.  
  3871.                                     $changed{$profile} = 1;
  3872.                                     UI_Info(
  3873.                                       sprintf(
  3874.                                         gettext('Adding #include <%s> to profile.'),
  3875.                                                 $inc));
  3876.                                     UI_Info(
  3877.                                       sprintf(
  3878.                                         gettext('Deleted %s previous matching profile entries.'),
  3879.                                                  $deleted)) if $deleted;
  3880.                                 } else {
  3881.  
  3882.                                     # stick the whole rule into the profile
  3883.                                     $sd{$profile}
  3884.                                        {$hat}
  3885.                        {allow}
  3886.                                        {netdomain}
  3887.                        {audit}
  3888.                                        {$family}
  3889.                                        {$sock_type} = $audit_toggle;
  3890.  
  3891.                                     $sd{$profile}
  3892.                                        {$hat}
  3893.                        {allow}
  3894.                                        {netdomain}
  3895.                        {rule}
  3896.                                        {$family}
  3897.                                        {$sock_type} = 1;
  3898.  
  3899.                                     # mark this profile as changed
  3900.                                     $changed{$profile} = 1;
  3901.  
  3902.                                     # give a little feedback to the user
  3903.                                     UI_Info(sprintf(
  3904.                                            gettext('Adding network access %s %s to profile.'),
  3905.                                                     $family,
  3906.                                                     $sock_type
  3907.                                                    )
  3908.                                            );
  3909.                                 }
  3910.                             } elsif ($ans eq "CMD_DENY") {
  3911.                                 $done = 1;
  3912.                 # record the new entry
  3913.                                     $sd{$profile}
  3914.                                        {$hat}
  3915.                        {deny}
  3916.                                        {netdomain}
  3917.                        {rule}
  3918.                                        {$family}
  3919.                                        {$sock_type} = 1;
  3920.  
  3921.                 $changed{$profile} = 1;
  3922.                                 UI_Info(sprintf(
  3923.                                         gettext('Denying network access %s %s to profile.'),
  3924.                                                 $family,
  3925.                                                 $sock_type
  3926.                                                )
  3927.                                        );
  3928.                             } else {
  3929.                                 redo;
  3930.                             }
  3931.                         }
  3932.                     }
  3933.                 }
  3934.             }
  3935.         }
  3936.     }
  3937. }
  3938.  
  3939. sub delete_net_duplicates {
  3940.     my ($netrules, $incnetrules) = @_;
  3941.     my $deleted = 0;
  3942.     if ( $incnetrules && $netrules ) {
  3943.         my $incnetglob = defined $incnetrules->{all};
  3944.  
  3945.         # See which if any profile rules are matched by the include and can be
  3946.         # deleted
  3947.         for my $fam ( keys %$netrules ) {
  3948.             if ( $incnetglob || (ref($incnetrules->{rule}{$fam}) ne "HASH" &&
  3949.                                  $incnetrules->{rule}{$fam} == 1)) { # include allows
  3950.                                                                # all net or
  3951.                                                                # all fam
  3952.                 if ( ref($netrules->{rule}{$fam}) eq "HASH" ) {
  3953.                     $deleted += ( keys %{$netrules->{rule}{$fam}} );
  3954.                 } else {
  3955.                     $deleted++;
  3956.                 }
  3957.                 delete $netrules->{rule}{$fam};
  3958.             } elsif ( ref($netrules->{rule}{$fam}) ne "HASH" &&
  3959.                       $netrules->{rule}{$fam} == 1 ){
  3960.                 next; # profile has all family
  3961.             } else {
  3962.                 for my $socket_type ( keys %{$netrules->{rule}{$fam}} )  {
  3963.                     if ( defined $incnetrules->{$fam}{$socket_type} ) {
  3964.                         delete $netrules->{$fam}{$socket_type};
  3965.                         $deleted++;
  3966.                     }
  3967.                 }
  3968.             }
  3969.         }
  3970.     }
  3971.     return $deleted;
  3972. }
  3973.  
  3974. sub delete_cap_duplicates ($$) {
  3975.     my ($profilecaps, $inccaps) = @_;
  3976.     my $deleted = 0;
  3977.     if ( $profilecaps && $inccaps ) {
  3978.         for my $capname ( keys %$profilecaps ) {
  3979.             if ( defined $inccaps->{$capname}{set} && $inccaps->{$capname}{set} == 1 ) {
  3980.                delete $profilecaps->{$capname};
  3981.                $deleted++;
  3982.             }
  3983.         }
  3984.     }
  3985.     return $deleted;
  3986. }
  3987.  
  3988. sub delete_path_duplicates ($$$) {
  3989.     my ($profile, $incname, $allow) = @_;
  3990.     my $deleted = 0;
  3991.  
  3992.     for my $entry (keys %{ $profile->{$allow}{path} }) {
  3993.         next if $entry eq "#include <$incname>";
  3994.     my ($cm, $am, @m) = match_include_to_path($incname, $allow, $entry);
  3995.         if ($cm
  3996.             && mode_contains($cm, $profile->{$allow}{path}{$entry}{mode})
  3997.         && mode_contains($am, $profile->{$allow}{path}{$entry}{audit}))
  3998.         {
  3999.             delete $profile->{$allow}{path}{$entry};
  4000.             $deleted++;
  4001.         }
  4002.     }
  4003.     return $deleted;
  4004. }
  4005.  
  4006. sub delete_duplicates (\%$) {
  4007.     my ( $profile, $incname ) = @_;
  4008.     my $deleted = 0;
  4009.  
  4010.     # don't cross delete allow rules covered by denied rules as the coverage
  4011.     # may not be complete.  ie. want to deny a subset of allow, allow a subset
  4012.     # of deny with different perms.
  4013.  
  4014.     ## network rules
  4015.     $deleted += delete_net_duplicates($profile->{allow}{netdomain}, $include{$incname}{$incname}{allow}{netdomain});
  4016.     $deleted += delete_net_duplicates($profile->{deny}{netdomain}, $include{$incname}{$incname}{deny}{netdomain});
  4017.  
  4018.     ## capabilities
  4019.     $deleted += delete_cap_duplicates($profile->{allow}{capability},
  4020.                      $include{$incname}{$incname}{allow}{capability});
  4021.     $deleted += delete_cap_duplicates($profile->{deny}{capability},
  4022.                      $include{$incname}{$incname}{deny}{capability});
  4023.  
  4024.     ## paths
  4025.     $deleted += delete_path_duplicates($profile, $incname, 'allow');
  4026.     $deleted += delete_path_duplicates($profile, $incname, 'deny');
  4027.  
  4028.     return $deleted;
  4029. }
  4030.  
  4031. sub matchnetinclude ($$$) {
  4032.     my ($incname, $family, $type) = @_;
  4033.  
  4034.     my @matches;
  4035.  
  4036.     # scan the include fragments for this profile looking for matches
  4037.     my @includelist = ($incname);
  4038.     my @checked;
  4039.     while (my $name = shift @includelist) {
  4040.         push @checked, $name;
  4041.         return 1
  4042.           if netrules_access_check($include{$name}{$name}{allow}{netdomain}, $family, $type);
  4043.         # if this fragment includes others, check them too
  4044.         if (keys %{ $include{$name}{$name}{include} } &&
  4045.             (grep($name, @checked) == 0) ) {
  4046.             push @includelist, keys %{ $include{$name}{$name}{include} };
  4047.         }
  4048.     }
  4049.     return 0;
  4050. }
  4051.  
  4052. sub matchcapincludes (\%$) {
  4053.     my ($profile, $cap) = @_;
  4054.  
  4055.     # check the path against the available set of include
  4056.     # files
  4057.     my @newincludes;
  4058.     my $includevalid;
  4059.     for my $incname (keys %include) {
  4060.     $includevalid = 0;
  4061.  
  4062.     # don't suggest it if we're already including it,
  4063.     # that's dumb
  4064.     next if $profile->{include}{$incname};
  4065.  
  4066.     # only match includes that can be suggested to
  4067.     # the user
  4068.     if ($cfg->{settings}{custom_includes}) {
  4069.         for my $incm (split(/\s+/,
  4070.                 $cfg->{settings}{custom_includes})) {
  4071.         $includevalid = 1 if $incname =~ /$incm/;
  4072.         }
  4073.     }
  4074.     $includevalid = 1 if $incname =~ /abstractions/;
  4075.     next if ($includevalid == 0);
  4076.  
  4077.     push @newincludes, $incname
  4078.         if ( defined $include{$incname}{$incname}{allow}{capability}{$cap}{set} &&
  4079.          $include{$incname}{$incname}{allow}{capability}{$cap}{set} == 1 );
  4080.     }
  4081.     return @newincludes;
  4082. }
  4083.  
  4084. sub matchnetincludes (\%$$) {
  4085.     my ($profile, $family, $type) = @_;
  4086.  
  4087.     # check the path against the available set of include
  4088.     # files
  4089.     my @newincludes;
  4090.     my $includevalid;
  4091.     for my $incname (keys %include) {
  4092.     $includevalid = 0;
  4093.  
  4094.     # don't suggest it if we're already including it,
  4095.     # that's dumb
  4096.     next if $profile->{include}{$incname};
  4097.  
  4098.     # only match includes that can be suggested to
  4099.     # the user
  4100.     if ($cfg->{settings}{custom_includes}) {
  4101.         for my $incm (split(/\s+/, $cfg->{settings}{custom_includes})) {
  4102.         $includevalid = 1 if $incname =~ /$incm/;
  4103.         }
  4104.     }
  4105.     $includevalid = 1 if $incname =~ /abstractions/;
  4106.     next if ($includevalid == 0);
  4107.  
  4108.     push @newincludes, $incname
  4109.         if matchnetinclude($incname, $family, $type);
  4110.     }
  4111.     return @newincludes;
  4112. }
  4113.  
  4114.  
  4115. sub do_logprof_pass {
  4116.     my $logmark = shift || "";
  4117.  
  4118.     # zero out the state variables for this pass...
  4119.     %t              = ( );
  4120.     %transitions    = ( );
  4121.     %seen           = ( );
  4122.     %sd             = ( );
  4123.     %profilechanges = ( );
  4124.     %prelog         = ( );
  4125.     @log            = ( );
  4126.     %log            = ( );
  4127.     %changed        = ( );
  4128.     %skip           = ( );
  4129.     %filelist       = ( );
  4130.  
  4131.     UI_Info(sprintf(gettext('Reading log entries from %s.'), $filename));
  4132.     UI_Info(sprintf(gettext('Updating AppArmor profiles in %s.'), $profiledir));
  4133.  
  4134.     readprofiles();
  4135.     unless ($sevdb) {
  4136.         $sevdb = new Immunix::Severity("$confdir/severity.db", gettext("unknown
  4137. "));
  4138.     }
  4139.  
  4140.     # we need to be able to break all the way out of deep into subroutine calls
  4141.     # if they select "Finish" so we can take them back out to the genprof prompt
  4142.     eval {
  4143.         unless ($repo_cfg || not defined $cfg->{repository}{url}) {
  4144.             $repo_cfg = read_config("repository.conf");
  4145.             unless ($repo_cfg->{repository}{enabled} eq "yes" ||
  4146.                     $repo_cfg->{repository}{enabled} eq "no") {
  4147.                 UI_ask_to_enable_repo();
  4148.             }
  4149.         }
  4150.  
  4151.         read_log($logmark);
  4152.  
  4153.         for my $root (@log) {
  4154.             handlechildren(undef, undef, $root);
  4155.         }
  4156.  
  4157.         for my $pid (sort { $a <=> $b } keys %profilechanges) {
  4158.             setprocess($pid, $profilechanges{$pid});
  4159.         }
  4160.  
  4161.         collapselog();
  4162.  
  4163.         ask_the_questions();
  4164.  
  4165.         if ($UI_Mode eq "yast") {
  4166.             if (not $running_under_genprof) {
  4167.                 if ($seenevents) {
  4168.                     my $w = { type => "wizard" };
  4169.                     $w->{explanation} = gettext("The profile analyzer has completed processing the log files.\n\nAll updated profiles will be reloaded");
  4170.                     $w->{functions} = [ "CMD_ABORT", "CMD_FINISHED" ];
  4171.                     SendDataToYast($w);
  4172.                     my $foo = GetDataFromYast();
  4173.                 } else {
  4174.                     my $w = { type => "wizard" };
  4175.                     $w->{explanation} = gettext("No unhandled AppArmor events were found in the system log.");
  4176.                     $w->{functions} = [ "CMD_ABORT", "CMD_FINISHED" ];
  4177.                     SendDataToYast($w);
  4178.                     my $foo = GetDataFromYast();
  4179.                 }
  4180.             }
  4181.         }
  4182.     };
  4183.  
  4184.     my $finishing = 0;
  4185.     if ($@) {
  4186.         if ($@ =~ /FINISHING/) {
  4187.             $finishing = 1;
  4188.         } else {
  4189.             die $@;
  4190.         }
  4191.     }
  4192.  
  4193.     save_profiles();
  4194.  
  4195.     if (repo_is_enabled()) {
  4196.         if ( (not defined $repo_cfg->{repository}{upload}) ||
  4197.              ($repo_cfg->{repository}{upload} eq "later") ) {
  4198.         UI_ask_to_upload_profiles();
  4199.         }
  4200.         if ($repo_cfg->{repository}{upload} eq "yes") {
  4201.             sync_profiles();
  4202.         }
  4203.         @created = ();
  4204.     }
  4205.  
  4206.     # if they hit "Finish" we need to tell the caller that so we can exit
  4207.     # all the way instead of just going back to the genprof prompt
  4208.     return $finishing ? "FINISHED" : "NORMAL";
  4209. }
  4210.  
  4211. sub save_profiles {
  4212.     # make sure the profile changes we've made are saved to disk...
  4213.     my @changed = sort keys %changed;
  4214.     #
  4215.     # first make sure that profiles in %changed are active (or actual profiles
  4216.     # in %sd) - this is to handle the sloppiness of setting profiles as changed
  4217.     # when they are parsed in the case of legacy hat code that we want to write
  4218.     # out in an updated format
  4219.     foreach  my $profile_name ( keys %changed ) {
  4220.         if ( ! is_active_profile( $profile_name ) ) {
  4221.             delete $changed{ $profile_name };
  4222.         }
  4223.     }
  4224.     @changed = sort keys %changed;
  4225.  
  4226.     if (@changed) {
  4227.         if ($UI_Mode eq "yast") {
  4228.             my (@selected_profiles, $title, $explanation, %profile_changes);
  4229.             foreach my $prof (@changed) {
  4230.                 my $oldprofile = serialize_profile($original_sd{$prof}, $prof);
  4231.                 my $newprofile = serialize_profile($sd{$prof}, $prof);
  4232.  
  4233.                 $profile_changes{$prof} = get_profile_diff($oldprofile,
  4234.                                                            $newprofile);
  4235.             }
  4236.             $explanation = gettext("Select which profile changes you would like to save to the\nlocal profile set");
  4237.             $title       = gettext("Local profile changes");
  4238.             SendDataToYast(
  4239.                 {
  4240.                     type           => "dialog-select-profiles",
  4241.                     title          => $title,
  4242.                     explanation    => $explanation,
  4243.                     default_select => "true",
  4244.                     get_changelog  => "false",
  4245.                     profiles       => \%profile_changes
  4246.                 }
  4247.             );
  4248.             my ($ypath, $yarg) = GetDataFromYast();
  4249.             if ($yarg->{STATUS} eq "cancel") {
  4250.                 return;
  4251.             } else {
  4252.                 my $selected_profiles_ref = $yarg->{PROFILES};
  4253.                 for my $profile (@$selected_profiles_ref) {
  4254.                     writeprofile_ui_feedback($profile);
  4255.                     reload_base($profile);
  4256.                 }
  4257.             }
  4258.         } else {
  4259.             my $q = {};
  4260.             $q->{title}   = "Changed Local Profiles";
  4261.             $q->{headers} = [];
  4262.  
  4263.             $q->{explanation} =
  4264.               gettext( "The following local profiles were changed.  Would you like to save them?");
  4265.  
  4266.             $q->{functions} = [ "CMD_SAVE_CHANGES",
  4267.                                 "CMD_VIEW_CHANGES",
  4268.                                 "CMD_ABORT", ];
  4269.  
  4270.             $q->{default} = "CMD_VIEW_CHANGES";
  4271.  
  4272.             $q->{options}  = [@changed];
  4273.             $q->{selected} = 0;
  4274.  
  4275.             my ($p, $ans, $arg);
  4276.             do {
  4277.                 ($ans, $arg) = UI_PromptUser($q);
  4278.  
  4279.                 if ($ans eq "CMD_VIEW_CHANGES") {
  4280.                     my $which      = $changed[$arg];
  4281.                     my $oldprofile =
  4282.                       serialize_profile($original_sd{$which}, $which);
  4283.                     my $newprofile = serialize_profile($sd{$which}, $which);
  4284.                     display_changes($oldprofile, $newprofile);
  4285.                 }
  4286.  
  4287.             } until $ans =~ /^CMD_SAVE_CHANGES/;
  4288.  
  4289.             for my $profile (sort keys %changed) {
  4290.                 writeprofile_ui_feedback($profile);
  4291.                 reload_base($profile);
  4292.             }
  4293.         }
  4294.     }
  4295. }
  4296.  
  4297.  
  4298. sub get_pager {
  4299.  
  4300.     if ( $ENV{PAGER} and (-x "/usr/bin/$ENV{PAGER}" ||
  4301.                           -x "/usr/sbin/$ENV{PAGER}" )
  4302.        ) {
  4303.         return $ENV{PAGER};
  4304.     } else {
  4305.         return "less"
  4306.     }
  4307. }
  4308.  
  4309.  
  4310. sub display_text {
  4311.     my ($header, $body) = @_;
  4312.     my $pager = get_pager();
  4313.     if (open(PAGER, "| $pager")) {
  4314.         print PAGER "$header\n\n$body";
  4315.         close(PAGER);
  4316.     }
  4317. }
  4318.  
  4319. sub get_profile_diff {
  4320.     my ($oldprofile, $newprofile) = @_;
  4321.     my $oldtmp = new File::Temp(UNLINK => 0);
  4322.     print $oldtmp $oldprofile;
  4323.     close($oldtmp);
  4324.  
  4325.     my $newtmp = new File::Temp(UNLINK => 0);
  4326.     print $newtmp $newprofile;
  4327.     close($newtmp);
  4328.  
  4329.     my $difftmp = new File::Temp(UNLINK => 0);
  4330.     my @diff;
  4331.     system("diff -u $oldtmp $newtmp > $difftmp");
  4332.     while (<$difftmp>) {
  4333.         push(@diff, $_) unless (($_ =~ /^(---|\+\+\+)/) ||
  4334.                                 ($_ =~ /^\@\@.*\@\@$/));
  4335.     }
  4336.     unlink($difftmp);
  4337.     unlink($oldtmp);
  4338.     unlink($newtmp);
  4339.     return join("", @diff);
  4340. }
  4341.  
  4342. sub display_changes {
  4343.     my ($oldprofile, $newprofile) = @_;
  4344.  
  4345.     my $oldtmp = new File::Temp( UNLINK => 0 );
  4346.     print $oldtmp $oldprofile;
  4347.     close($oldtmp);
  4348.  
  4349.     my $newtmp = new File::Temp( UNLINK => 0 );
  4350.     print $newtmp $newprofile;
  4351.     close($newtmp);
  4352.  
  4353.     my $difftmp = new File::Temp(UNLINK => 0);
  4354.     my @diff;
  4355.     system("diff -u $oldtmp $newtmp > $difftmp");
  4356.     if ($UI_Mode eq "yast") {
  4357.         while (<$difftmp>) {
  4358.             push(@diff, $_) unless (($_ =~ /^(---|\+\+\+)/) ||
  4359.                                     ($_ =~ /^\@\@.*\@\@$/));
  4360.         }
  4361.         UI_LongMessage(gettext("Profile Changes"), join("", @diff));
  4362.     } else {
  4363.         system("less $difftmp");
  4364.     }
  4365.  
  4366.     unlink($difftmp);
  4367.     unlink($oldtmp);
  4368.     unlink($newtmp);
  4369. }
  4370.  
  4371. sub setprocess ($$) {
  4372.     my ($pid, $profile) = @_;
  4373.  
  4374.     # don't do anything if the process exited already...
  4375.     return unless -e "/proc/$pid/attr/current";
  4376.  
  4377.     return unless open(CURR, "/proc/$pid/attr/current");
  4378.     my $current = <CURR>;
  4379.     return unless $current;
  4380.     chomp $current;
  4381.     close(CURR);
  4382.  
  4383.     # only change null profiles
  4384.     return unless $current =~ /null(-complain)*-profile/;
  4385.  
  4386.     return unless open(STAT, "/proc/$pid/stat");
  4387.     my $stat = <STAT>;
  4388.     chomp $stat;
  4389.     close(STAT);
  4390.  
  4391.     return unless $stat =~ /^\d+ \((\S+)\) /;
  4392.     my $currprog = $1;
  4393.  
  4394.     open(CURR, ">/proc/$pid/attr/current") or return;
  4395.     print CURR "setprofile $profile";
  4396.     close(CURR);
  4397. }
  4398.  
  4399. sub collapselog () {
  4400.     for my $sdmode (keys %prelog) {
  4401.         for my $profile (keys %{ $prelog{$sdmode} }) {
  4402.             for my $hat (keys %{ $prelog{$sdmode}{$profile} }) {
  4403.                 for my $path (keys %{ $prelog{$sdmode}{$profile}{$hat}{path} }) {
  4404.  
  4405.                     my $mode = $prelog{$sdmode}{$profile}{$hat}{path}{$path};
  4406.  
  4407.                     # we want to ignore anything from the log that's already
  4408.                     # in the profile
  4409.                     my $combinedmode = 0;
  4410.  
  4411.                     # is it in the original profile?
  4412.                     if ($sd{$profile}{$hat}{allow}{path}{$path}) {
  4413.                         $combinedmode |= $sd{$profile}{$hat}{allow}{path}{$path}{mode};
  4414.                     }
  4415.  
  4416.                     # does path match any regexps in original profile?
  4417.                     $combinedmode |= rematchfrag($sd{$profile}{$hat}, 'allow', $path);
  4418.  
  4419.                     # does path match anything pulled in by includes in
  4420.                     # original profile?
  4421.                     $combinedmode |= match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $path);
  4422.  
  4423.                     # if we found any matching entries, do the modes match?
  4424.                     unless ($combinedmode && mode_contains($combinedmode, $mode)) {
  4425.  
  4426.                         # merge in any previous modes from this run
  4427.                         if ($log{$sdmode}{$profile}{$hat}{$path}) {
  4428.                             $mode |= $log{$sdmode}{$profile}{$hat}{path}{$path};
  4429.                         }
  4430.  
  4431.                         # record the new entry
  4432.                         $log{$sdmode}{$profile}{$hat}{path}{$path} = $mode;
  4433.                     }
  4434.                 }
  4435.  
  4436.                 for my $capability (keys %{ $prelog{$sdmode}{$profile}{$hat}{capability} }) {
  4437.  
  4438.                     # if we don't already have this capability in the profile,
  4439.                     # add it
  4440.                     unless ($sd{$profile}{$hat}{allow}{capability}{$capability}{set}) {
  4441.                         $log{$sdmode}{$profile}{$hat}{capability}{$capability} = 1;
  4442.                     }
  4443.                 }
  4444.  
  4445.                 # Network toggle handling
  4446.                 my $ndref = $prelog{$sdmode}{$profile}{$hat}{netdomain};
  4447.                 for my $family ( keys %{$ndref} ) {
  4448.                     for my $sock_type ( keys %{$ndref->{$family}} ) {
  4449.                         unless ( profile_known_network($sd{$profile}{$hat},
  4450.                                $family, $sock_type)) {
  4451.                             $log{$sdmode}
  4452.                                 {$profile}
  4453.                                 {$hat}
  4454.                                 {netdomain}
  4455.                                 {$family}
  4456.                                 {$sock_type}=1;
  4457.                         }
  4458.                     }
  4459.                 }
  4460.             }
  4461.         }
  4462.     }
  4463. }
  4464.  
  4465. sub profilemode ($) {
  4466.     my $mode = shift;
  4467.  
  4468.     my $modifier = ($mode =~ m/[iupUP]/)[0];
  4469.     if ($modifier) {
  4470.         $mode =~ s/[iupUPx]//g;
  4471.         $mode .= $modifier . "x";
  4472.     }
  4473.  
  4474.     return $mode;
  4475. }
  4476.  
  4477. # kinky.
  4478. sub commonprefix (@) { (join("\0", @_) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/)[0] }
  4479. sub commonsuffix (@) { reverse(((reverse join("\0", @_)) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/)[0]); }
  4480.  
  4481. sub uniq (@) {
  4482.     my %seen;
  4483.     my @result = sort grep { !$seen{$_}++ } @_;
  4484.     return @result;
  4485. }
  4486.  
  4487. our $MODE_MAP_RE = "r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N";
  4488. our $LOG_MODE_RE = "r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|Cx|Nx|Pix|Cix";
  4489. our $PROFILE_MODE_RE = "r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|Cx|Pix|Cix";
  4490. our $PROFILE_MODE_NT_RE = "r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|Cx|Pix|Cix";
  4491. our $PROFILE_MODE_DENY_RE = "r|w|l|m|k|a|x";
  4492.  
  4493. sub split_log_mode($) {
  4494.     my $mode = shift;
  4495.     my $user = "";
  4496.     my $other = "";
  4497.  
  4498.     if ($mode =~ /(.*?)::(.*)/) {
  4499.     $user = $1 if ($1);
  4500.     $other = $2 if ($2);
  4501.     } else {
  4502.     $user = $mode;
  4503.     $other = $mode;
  4504.     }
  4505.     return ($user, $other);
  4506. }
  4507.  
  4508. sub map_log_mode ($) {
  4509.     my $mode = shift;
  4510.     return $mode;
  4511. #    $mode =~ s/(.*l.*)::.*/$1/ge;
  4512. #    $mode =~ s/.*::(.*l.*)/$1/ge;
  4513. #    $mode =~ s/:://;
  4514. #     return $mode;
  4515. #    return $1;
  4516. }
  4517.  
  4518. sub hide_log_mode($) {
  4519.     my $mode = shift;
  4520.  
  4521.     $mode =~ s/:://;
  4522.     return $mode;
  4523. }
  4524.  
  4525. sub validate_log_mode ($) {
  4526.     my $mode = shift;
  4527.  
  4528.     return ($mode =~ /^($LOG_MODE_RE)+$/) ? 1 : 0;
  4529. }
  4530.  
  4531. sub validate_profile_mode ($$$) {
  4532.     my ($mode, $allow, $nt_name) = @_;
  4533.  
  4534.     if ($allow eq 'deny') {
  4535.     return ($mode =~ /^($PROFILE_MODE_DENY_RE)+$/) ? 1 : 0;
  4536.     } elsif ($nt_name) {
  4537.     return ($mode =~ /^($PROFILE_MODE_NT_RE)+$/) ? 1 : 0;
  4538.     }
  4539.  
  4540.     return ($mode =~ /^($PROFILE_MODE_RE)+$/) ? 1 : 0;
  4541. }
  4542.  
  4543. # modes internally are stored as a bit Mask
  4544. sub sub_str_to_mode($) {
  4545.     my $str = shift;
  4546.     my $mode = 0;
  4547.  
  4548.     return 0 if (not $str);
  4549.  
  4550.     while ($str =~ s/(${MODE_MAP_RE})//) {
  4551.     my $tmp = $1;
  4552. #print "found mode $1\n";
  4553.  
  4554.     if ($tmp && $MODE_HASH{$tmp}) {
  4555.         $mode |= $MODE_HASH{$tmp};
  4556.     } else {
  4557. #print "found mode $tmp\n";
  4558.     }
  4559.     }
  4560.  
  4561. #my $tmp = mode_to_str($mode);
  4562. #print "parsed_mode $mode\n";
  4563.     return $mode;
  4564. }
  4565.  
  4566. sub print_mode ($) {
  4567.     my $mode = shift;
  4568.  
  4569.     my ($user, $other) = split_mode($mode);
  4570.  
  4571.     my $str = sub_str_to_mode($user) . "::" . sub_str_to_mode($other);
  4572.  
  4573.     return $str;
  4574. }
  4575.  
  4576. sub str_to_mode ($) {
  4577.     my $str = shift;
  4578.  
  4579.     return 0 if (not $str);
  4580.  
  4581.     my ($user, $other) = split_log_mode($str);
  4582.  
  4583. #print "str: $str  user: $user, other $other\n";
  4584.     # we only allow user or all
  4585.     $user = $other if (!$user);
  4586.  
  4587.     my $mode = sub_str_to_mode($user);
  4588.     $mode |= (sub_str_to_mode($other) << $AA_OTHER_SHIFT);
  4589.  
  4590. #print "user: $user " .sub_str_to_mode($user) . " other: $other " . (sub_str_to_mode($other) << $AA_OTHER_SHIFT) . " mode = $mode\n";
  4591.  
  4592.     return $mode;
  4593. }
  4594.  
  4595. sub log_str_to_mode($$$) {
  4596.     my ($profile, $str, $nt_name) = @_;
  4597.  
  4598.     my $mode = str_to_mode($str);
  4599.  
  4600.     # this will cover both nx and nix
  4601.     if (contains($mode, "Nx")) {
  4602.     # need to transform to px, cx
  4603.  
  4604.     if ($nt_name =~ /(.+?)\/\/(.+?)/) {
  4605.         my ($lprofile, $lhat) = @_;
  4606.         my $tmode = 0;
  4607.         if ($profile eq $profile) {
  4608.         if ($mode & ($AA_MAY_EXEC)) {
  4609.             $tmode = str_to_mode("Cx::");
  4610.         }
  4611.         if ($mode & ($AA_MAY_EXEC << $AA_OTHER_SHIFT)) {
  4612.             $tmode |= str_to_mode("Cx");
  4613.         }
  4614.         $nt_name = $lhat;
  4615.         } else {
  4616.         if ($mode & ($AA_MAY_EXEC)) {
  4617.             $tmode = str_to_mode("Px::");
  4618.         }
  4619.         if ($mode & ($AA_MAY_EXEC << $AA_OTHER_SHIFT)) {
  4620.             $tmode |= str_to_mode("Px");
  4621.         }
  4622.         $nt_name = $lhat;
  4623.         }
  4624.         $mode = ($mode & ~(str_to_mode("Nx")));
  4625.         $mode |= $tmode;
  4626.     }
  4627.     }
  4628.     return ($mode, $nt_name);
  4629. }
  4630.  
  4631. sub split_mode ($) {
  4632.     my $mode = shift;
  4633.  
  4634.     my $user = $mode & $AA_USER_MASK;
  4635.     my $other = ($mode >> $AA_OTHER_SHIFT) & $AA_USER_MASK;
  4636.  
  4637.     return ($user, $other);
  4638. }
  4639.  
  4640. sub is_user_mode ($) {
  4641.     my $mode = shift;
  4642.  
  4643.     my ($user, $other) = split_mode($mode);
  4644.  
  4645.     if ($user && !$other) {
  4646.     return 1;
  4647.     }
  4648.     return 0;
  4649. }
  4650.  
  4651. sub sub_mode_to_str($) {
  4652.     my $mode = shift;
  4653.     my $str = "";
  4654.  
  4655.     # "w" implies "a"
  4656.     $mode &= (~$AA_MAY_APPEND) if ($mode & $AA_MAY_WRITE);
  4657.     $str .= "m" if ($mode & $AA_EXEC_MMAP);
  4658.     $str .= "r" if ($mode & $AA_MAY_READ);
  4659.     $str .= "w" if ($mode & $AA_MAY_WRITE);
  4660.     $str .= "a" if ($mode & $AA_MAY_APPEND);
  4661.     $str .= "l" if ($mode & $AA_MAY_LINK);
  4662.     $str .= "k" if ($mode & $AA_MAY_LOCK);
  4663.     if ($mode & $AA_EXEC_UNCONFINED) {
  4664.     if ($mode & $AA_EXEC_UNSAFE) {
  4665.         $str .= "u";
  4666.     } else {
  4667.         $str .= "U";
  4668.     }
  4669.     }
  4670.     if ($mode & ($AA_EXEC_PROFILE | $AA_EXEC_NT)) {
  4671.     if ($mode & $AA_EXEC_UNSAFE) {
  4672.         $str .= "p";
  4673.     } else {
  4674.         $str .= "P";
  4675.     }
  4676.     }
  4677.     if ($mode & $AA_EXEC_CHILD) {
  4678.     if ($mode & $AA_EXEC_UNSAFE) {
  4679.         $str .= "c";
  4680.     } else {
  4681.         $str .= "C";
  4682.     }
  4683.     }
  4684.     $str .= "i" if ($mode & $AA_EXEC_INHERIT);
  4685.     $str .= "x" if ($mode & $AA_MAY_EXEC);
  4686.  
  4687.     return $str;
  4688. }
  4689.  
  4690. sub flatten_mode ($) {
  4691.     my $mode = shift;
  4692.  
  4693.     return 0 if (!$mode);
  4694.  
  4695.     $mode = ($mode & $AA_USER_MASK) | (($mode >> $AA_OTHER_SHIFT) & $AA_USER_MASK);
  4696.     $mode |= ($mode << $AA_OTHER_SHIFT);
  4697. }
  4698.  
  4699. sub mode_to_str ($) {
  4700.     my $mode = shift;
  4701.     $mode = flatten_mode($mode);
  4702.     return sub_mode_to_str($mode);
  4703. }
  4704.  
  4705. sub owner_flatten_mode($) {
  4706.     my $mode = shift;
  4707.     $mode = flatten_mode($mode) & $AA_USER_MASK;
  4708.     return $mode;
  4709. }
  4710.  
  4711. sub mode_to_str_user ($) {
  4712.     my $mode = shift;
  4713.  
  4714.     my ($user, $other) = split_mode($mode);
  4715.  
  4716.     my $str = "";
  4717.     $user = 0 if (!$user);
  4718.     $other = 0 if (!$other);
  4719.  
  4720.     if ($user & ~$other) {
  4721.     # more user perms than other
  4722.     $str = sub_mode_to_str($other). " + " if ($other);
  4723.     $str .= "owner " . sub_mode_to_str($user & ~$other);
  4724.     } elsif (is_user_mode($mode)) {
  4725.     $str = "owner " . sub_mode_to_str($user);
  4726.     } else {
  4727.     $str = sub_mode_to_str(flatten_mode($mode));
  4728.     }
  4729.     return $str;
  4730. }
  4731.  
  4732. sub mode_contains ($$) {
  4733.     my ($mode, $subset) = @_;
  4734.  
  4735.     # "w" implies "a"
  4736.     if ($mode & $AA_MAY_WRITE) {
  4737.     $mode |= $AA_MAY_APPEND;
  4738.     }
  4739.     if ($mode & ($AA_MAY_WRITE << $AA_OTHER_SHIFT)) {
  4740.     $mode |= ($AA_MAY_APPEND << $AA_OTHER_SHIFT);
  4741.     }
  4742.  
  4743.     # "?ix" implies "m"
  4744.     if ($mode & $AA_EXEC_INHERIT) {
  4745.     $mode |= $AA_EXEC_MMAP;
  4746.     }
  4747.     if ($mode & ($AA_EXEC_INHERIT << $AA_OTHER_SHIFT)) {
  4748.     $mode |= ($AA_EXEC_MMAP << $AA_OTHER_SHIFT);
  4749.     }
  4750.  
  4751.     return (($mode & $subset) == $subset);
  4752. }
  4753.  
  4754. sub contains ($$) {
  4755.     my ($mode, $str) = @_;
  4756.  
  4757.     return mode_contains($mode, str_to_mode($str));
  4758. }
  4759.  
  4760. # isSkippableFile - return true if filename matches something that
  4761. # should be skipped (rpm backup files, dotfiles, emacs backup files
  4762. # Annoyingly, this needs to be kept in sync with the skipped files
  4763. # in the apparmor initscript.
  4764. sub isSkippableFile($) {
  4765.     my $path = shift;
  4766.  
  4767.     return ($path =~ /(^|\/)\.[^\/]*$/
  4768.             || $path =~ /\.rpm(save|new)$/
  4769.             || $path =~ /\.dpkg-(old|new)$/
  4770.         || $path =~ /\.swp$/
  4771.             || $path =~ /\~$/);
  4772. }
  4773.  
  4774. sub checkIncludeSyntax($) {
  4775.     my $errors = shift;
  4776.  
  4777.     if (opendir(SDDIR, $profiledir)) {
  4778.         my @incdirs = grep { (!/^\./) && (-d "$profiledir/$_") } readdir(SDDIR);
  4779.         close(SDDIR);
  4780.         while (my $id = shift @incdirs) {
  4781.             if (opendir(SDDIR, "$profiledir/$id")) {
  4782.                 for my $path (grep { !/^\./ } readdir(SDDIR)) {
  4783.                     chomp($path);
  4784.                     next if isSkippableFile($path);
  4785.                     if (-f "$profiledir/$id/$path") {
  4786.                         my $file = "$id/$path";
  4787.                         $file =~ s/$profiledir\///;
  4788.                         eval { loadinclude($file); };
  4789.                         if ( defined $@ && $@ ne "" ) {
  4790.                             push @$errors, $@;
  4791.                         }
  4792.                     } elsif (-d "$id/$path") {
  4793.                         push @incdirs, "$id/$path";
  4794.                     }
  4795.                 }
  4796.                 closedir(SDDIR);
  4797.             }
  4798.         }
  4799.     }
  4800.     return $errors;
  4801. }
  4802.  
  4803. sub checkProfileSyntax ($) {
  4804.     my $errors = shift;
  4805.  
  4806.     # Check the syntax of profiles
  4807.  
  4808.     opendir(SDDIR, $profiledir)
  4809.       or fatal_error "Can't read AppArmor profiles in $profiledir.";
  4810.     for my $file (grep { -f "$profiledir/$_" } readdir(SDDIR)) {
  4811.         next if isSkippableFile($file);
  4812.         my $err = readprofile("$profiledir/$file", \&printMessageErrorHandler, 1);
  4813.         if (defined $err and $err ne "") {
  4814.             push @$errors, $err;
  4815.         }
  4816.     }
  4817.     closedir(SDDIR);
  4818.     return $errors;
  4819. }
  4820.  
  4821. sub printMessageErrorHandler ($) {
  4822.     my $message = shift;
  4823.     return $message;
  4824. }
  4825.  
  4826. sub readprofiles () {
  4827.     opendir(SDDIR, $profiledir)
  4828.       or fatal_error "Can't read AppArmor profiles in $profiledir.";
  4829.     for my $file (grep { -f "$profiledir/$_" } readdir(SDDIR)) {
  4830.         next if isSkippableFile($file);
  4831.         readprofile("$profiledir/$file", \&fatal_error, 1);
  4832.     }
  4833.     closedir(SDDIR);
  4834. }
  4835.  
  4836. sub readinactiveprofiles () {
  4837.     return if ( ! -e $extraprofiledir );
  4838.     opendir(ESDDIR, $extraprofiledir) or
  4839.       fatal_error "Can't read AppArmor profiles in $extraprofiledir.";
  4840.     for my $file (grep { -f "$extraprofiledir/$_" } readdir(ESDDIR)) {
  4841.         next if $file =~ /\.rpm(save|new)|README$/;
  4842.         readprofile("$extraprofiledir/$file", \&fatal_error, 0);
  4843.     }
  4844.     closedir(ESDDIR);
  4845. }
  4846.  
  4847. sub readprofile ($$$) {
  4848.     my $file          = shift;
  4849.     my $error_handler = shift;
  4850.     my $active_profile = shift;
  4851.     if (open(SDPROF, "$file")) {
  4852.         local $/;
  4853.         my $data = <SDPROF>;
  4854.         close(SDPROF);
  4855.  
  4856.         eval {
  4857.             my $profile_data = parse_profile_data($data, $file, 0);
  4858.             if ($profile_data && $active_profile) {
  4859.                 attach_profile_data(\%sd, $profile_data);
  4860.                 attach_profile_data(\%original_sd, $profile_data);
  4861.             } elsif ( $profile_data ) {
  4862.                 attach_profile_data(\%extras,      $profile_data);
  4863.             }
  4864.         };
  4865.  
  4866.         # if there were errors loading the profile, call the error handler
  4867.         if ($@) {
  4868.             $@ =~ s/\n$//;
  4869.             return &$error_handler($@);
  4870.         }
  4871.     } else {
  4872.         $DEBUGGING && debug "readprofile: can't read $file - skipping";
  4873.     }
  4874. }
  4875.  
  4876. sub attach_profile_data {
  4877.     my ($profiles, $profile_data) = @_;
  4878.  
  4879.     # make deep copies of the profile data so that if we change one set of
  4880.     # profile data, we're not changing others because of sharing references
  4881.     for my $p ( keys %$profile_data) {
  4882.           $profiles->{$p} = dclone($profile_data->{$p});
  4883.     }
  4884. }
  4885.  
  4886. sub parse_profile_data {
  4887.     my ($data, $file, $do_include) = @_;
  4888.  
  4889.  
  4890.     my ($profile_data, $profile, $hat, $in_contained_hat, $repo_data,
  4891.         @parsed_profiles);
  4892.     my $initial_comment = "";
  4893.  
  4894.     if ($do_include) {
  4895.     $profile = $file;
  4896.     $hat = $file;
  4897.     }
  4898.  
  4899.     for (split(/\n/, $data)) {
  4900.         chomp;
  4901.  
  4902.         # we don't care about blank lines
  4903.         next if /^\s*$/;
  4904.  
  4905.         # start of a profile...
  4906.         if (m/^\s*(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$/) {
  4907.         if ($do_include) {
  4908.         die "include <$file> contains syntax errors.\n";
  4909.         }
  4910.  
  4911.             # if we run into the start of a profile while we're already in a
  4912.             # profile, something's wrong...
  4913.             if ($profile) {
  4914.         unless (($profile eq $hat) and $4) {
  4915.             die "$profile profile in $file contains syntax errors.\n";
  4916.         }
  4917.         }
  4918.  
  4919.             # we hit the start of a profile, keep track of it...
  4920.         if ($profile && ($profile eq $hat) && $4) {
  4921.         # local profile
  4922.         $hat = $4;
  4923.         $in_contained_hat = 1;
  4924.         $profile_data->{$profile}{$hat}{profile} = 1;
  4925.         } else {
  4926.         $profile  = $2 || $4;
  4927.         # hat is same as profile name if we're not in a hat
  4928.         ($profile, $hat) = split /\/\//, $profile;
  4929.         $in_contained_hat = 0;
  4930.         if ($hat) {
  4931.             $profile_data->{$profile}{$hat}{external} = 1;
  4932.         }
  4933.  
  4934.         $hat ||= $profile;
  4935.         }
  4936.  
  4937.             my $flags = $7;
  4938.  
  4939.             # deal with whitespace in profile and hat names.
  4940.             $profile = strip_quotes($profile);
  4941.             $hat     = strip_quotes($hat) if $hat;
  4942.  
  4943.         # save off the name and filename
  4944.         $profile_data->{$profile}{$hat}{name} = $profile;
  4945.         $profile_data->{$profile}{$hat}{filename} = $file;
  4946.         $filelist{$file}{profiles}{$profile}{$hat} = 1;
  4947.  
  4948.             # keep track of profile flags
  4949.         $profile_data->{$profile}{$hat}{flags} = $flags;
  4950.  
  4951.             $profile_data->{$profile}{$hat}{allow}{netdomain} = { };
  4952.             $profile_data->{$profile}{$hat}{allow}{path} = { };
  4953.  
  4954.             # store off initial comment if they have one
  4955.             $profile_data->{$profile}{$hat}{initial_comment} = $initial_comment
  4956.               if $initial_comment;
  4957.             $initial_comment = "";
  4958.  
  4959.             if ($repo_data) {
  4960.                 $profile_data->{$profile}{$profile}{repo}{url}  = $repo_data->{url};
  4961.                 $profile_data->{$profile}{$profile}{repo}{user} = $repo_data->{user};
  4962.                 $profile_data->{$profile}{$profile}{repo}{id}   = $repo_data->{id};
  4963.                 $repo_data = undef;
  4964.             }
  4965.  
  4966.         } elsif (m/^\s*\}\s*(#.*)?$/) { # end of a profile...
  4967.  
  4968.             # if we hit the end of a profile when we're not in one, something's
  4969.             # wrong...
  4970.         if ($do_include) {
  4971.         die "include <$file> contains syntax errors.";
  4972.         }
  4973.             if (not $profile) {
  4974.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  4975.             }
  4976.  
  4977.             if ($in_contained_hat) {
  4978.                 $hat = $profile;
  4979.                 $in_contained_hat = 0;
  4980.             } else {
  4981.                 push @parsed_profiles, $profile;
  4982.                 # mark that we're outside of a profile now...
  4983.                 $profile = undef;
  4984.             }
  4985.  
  4986.             $initial_comment = "";
  4987.  
  4988.         } elsif (m/^\s*(audit\s+)?(deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$/) {  # capability entry
  4989.             if (not $profile) {
  4990.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  4991.             }
  4992.  
  4993.         my $audit = $1 ? 1 : 0;
  4994.         my $allow = $2 ? 'deny' : 'allow';
  4995.         $allow = 'deny' if ($2);
  4996.             my $capability = $3;
  4997.             $profile_data->{$profile}{$hat}{$allow}{capability}{$capability}{set} = 1;
  4998.             $profile_data->{$profile}{$hat}{$allow}{capability}{$capability}{audit} = $audit;
  4999.         } elsif (m/^\s*set capability\s+(\S+)\s*,\s*(#.*)?$/) {  # capability entry
  5000.             if (not $profile) {
  5001.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5002.             }
  5003.  
  5004.             my $capability = $1;
  5005.             $profile_data->{$profile}{$hat}{set_capability}{$capability} = 1;
  5006.  
  5007.     } elsif (m/^\s*(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$/) { # for now just keep link
  5008.             if (not $profile) {
  5009.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5010.             }
  5011.         my $audit = $1 ? 1 : 0;
  5012.         my $allow = $2 ? 'deny' : 'allow';
  5013.  
  5014.         my $subset = $4;
  5015.             my $link = strip_quotes($7);
  5016.         my $value = strip_quotes($8);
  5017.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{to} = $value;
  5018.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{mode} = $AA_MAY_LINK;
  5019.         if ($subset) {
  5020.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{mode} = $AA_LINK_SUBSET;
  5021.         }
  5022.         if ($audit) {
  5023.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{audit} = $AA_LINK_SUBSET;
  5024.         } else {
  5025.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{audit} = 0;
  5026.         }
  5027.  
  5028.     } elsif (m/^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$/) { # for now just keep change_profile
  5029.             if (not $profile) {
  5030.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5031.             }
  5032.             my $cp = strip_quotes($1);
  5033.  
  5034.             $profile_data->{$profile}{$hat}{change_profile}{$cp} = 1;
  5035.     } elsif (m/^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$/) { # never do anything with aliases just keep them
  5036.             my $from = strip_quotes($1);
  5037.         my $to = strip_quotes($2);
  5038.  
  5039.             if ($profile) {
  5040.         $profile_data->{$profile}{$hat}{alias}{$from} = $to;
  5041.         } else {
  5042.         unless (exists $filelist{$file}) {
  5043.             $filelist{$file} = { };
  5044.         }
  5045.         $filelist{$file}{alias}{$from} = $to;
  5046.         }
  5047.  
  5048.        } elsif (m/^\s*set\s+rlimit\s+(.+)\s+<=\s*(.+)\s*,(#.*)?$/) { # never do anything with rlimits just keep them
  5049.        if (not $profile) {
  5050.            die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5051.        }
  5052.        my $from = $1;
  5053.            my $to = $2;
  5054.  
  5055.        $profile_data->{$profile}{$hat}{rlimit}{$from} = $to;
  5056.  
  5057.         } elsif (/^\s*(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$/i) { # boolean definition
  5058.        if (not $profile) {
  5059.            die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5060.        }
  5061.        my $bool_var = $1;
  5062.            my $value = $2;
  5063.  
  5064.        $profile_data->{$profile}{$hat}{lvar}{$bool_var} = $value;
  5065.         } elsif (/^\s*(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\+?=\s*(.+?)\s*,?\s*(#.*)?$/) { # variable additions both += and = doesn't mater
  5066.        my $list_var = strip_quotes($1);
  5067.            my $value = strip_quotes($2);
  5068.  
  5069.        if ($profile) {
  5070.            unless (exists $profile_data->{$profile}{$hat}{lvar}) {
  5071.            # create lval hash by sticking an empty list into list_var
  5072.            my @empty = ();
  5073.            $profile_data->{$profile}{$hat}{lvar}{$list_var} = \@empty;
  5074.            }
  5075.  
  5076.            store_list_var($profile_data->{$profile}{$hat}{lvar}, $list_var, $value);
  5077.        } else  {
  5078.            unless (exists $filelist{$file}{lvar}) {
  5079.            # create lval hash by sticking an empty list into list_var
  5080.            my @empty = ();
  5081.            $filelist{$file}{lvar}{$list_var} = \@empty;
  5082.            }
  5083.  
  5084.            store_list_var($filelist{$file}{lvar}, $list_var, $value);
  5085.        }
  5086.         } elsif (m/^\s*if\s+(not\s+)?(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*\{\s*(#.*)?$/) { # conditional -- boolean
  5087.         } elsif (m/^\s*if\s+(not\s+)?defined\s+(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$/) { # conditional -- variable defined
  5088.         } elsif (m/^\s*if\s+(not\s+)?defined\s+(\$\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$/) { # conditional -- boolean defined
  5089.         } elsif (m/^\s*(audit\s+)?(deny\s+)?(owner\s+)?([\"\@\/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$/) {     # path entry
  5090.             if (not $profile) {
  5091.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5092.             }
  5093.  
  5094.         my $audit = $1 ? 1 : 0;
  5095.         my $allow = $2 ? 'deny' : 'allow';
  5096.         my $user = $3 ? 1 : 0;
  5097.             my ($path, $mode, $nt_name) = ($4, $5, $7);
  5098.  
  5099.             # strip off any trailing spaces.
  5100.             $path =~ s/\s+$//;
  5101.             $nt_name =~ s/\s+$// if $nt_name;
  5102.  
  5103.             $path = strip_quotes($path);
  5104.             $nt_name = strip_quotes($nt_name) if $nt_name;
  5105.  
  5106.             # make sure they don't have broken regexps in the profile
  5107.             my $p_re = convert_regexp($path);
  5108.             eval { "foo" =~ m/^$p_re$/; };
  5109.             if ($@) {
  5110.                 die sprintf(gettext('Profile %s contains invalid regexp %s.'),
  5111.                                      $file, $path) . "\n";
  5112.             }
  5113.  
  5114.             if (!validate_profile_mode($mode, $allow, $nt_name)) {
  5115.                 fatal_error(sprintf(gettext('Profile %s contains invalid mode %s.'), $file, $mode));
  5116.             }
  5117.  
  5118.         my $tmpmode;
  5119.         if ($user) {
  5120.         $tmpmode = str_to_mode("${mode}::");
  5121.         } else {
  5122.         $tmpmode = str_to_mode($mode);
  5123.         }
  5124.             $profile_data->{$profile}{$hat}{$allow}{path}{$path}{mode} = $tmpmode;
  5125.             $profile_data->{$profile}{$hat}{$allow}{path}{$path}{to} = $nt_name if $nt_name;
  5126.         if ($audit) {
  5127.         $profile_data->{$profile}{$hat}{$allow}{path}{$path}{audit} = $tmpmode;
  5128.         } else {
  5129.         $profile_data->{$profile}{$hat}{$allow}{path}{$path}{audit} = 0;
  5130.         }
  5131.         } elsif (m/^\s*#include <(.+)>\s*$/) {     # include stuff
  5132.             my $include = $1;
  5133.  
  5134.             if ($profile) {
  5135.                 $profile_data->{$profile}{$hat}{include}{$include} = 1;
  5136.             } else {
  5137.                 unless (exists $filelist{$file}) {
  5138.                    $filelist{$file} = { };
  5139.                 }
  5140.                 $filelist{$file}{include}{$include} = 1;
  5141.             }
  5142.  
  5143.             # try to load the include...
  5144.             my $ret = eval { loadinclude($include); };
  5145.             # propagate errors up the chain
  5146.             if ($@) { die $@; }
  5147.  
  5148.             return $ret if ( $ret != 0 );
  5149.  
  5150.         } elsif (/^\s*(audit\s+)?(deny\s+)?network(.*)/) {
  5151.             if (not $profile) {
  5152.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5153.             }
  5154.         my $audit = $1 ? 1 : 0;
  5155.         my $allow = $2 ? 'deny' : 'allow';
  5156.         my $network = $3;
  5157.  
  5158.             unless ($profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}) {
  5159.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule} = { };
  5160.             }
  5161.  
  5162.             if ( $network =~ /\s+(\S+)\s*,\s*(#.*)?$/ ) {
  5163.         my $fam = $1;
  5164.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{$fam} = 1;
  5165.         $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{$fam} = $audit;
  5166.             } elsif ($network =~ /\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$/ ) {
  5167.         my $fam = $1;
  5168.         my $type = $2;
  5169.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{$fam}{$type} = 1;
  5170.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{$fam}{$type} = $audit;
  5171.             } else {
  5172.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{all} = 1;
  5173.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{all} = 1;
  5174.             }
  5175.         } elsif (/^\s*(tcp_connect|tcp_accept|udp_send|udp_receive)/) {
  5176. # just ignore and drop old style network
  5177. #        die sprintf(gettext('%s contains old style network rules.'), $file) . "\n";
  5178.  
  5179.         } elsif (m/^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$/) {
  5180.         if (not $profile) {
  5181.         die "$file contains syntax errors.";
  5182.         }
  5183.         # change_hat declaration - needed to change_hat to an external
  5184.         # hat
  5185.             $hat = $1;
  5186.             $hat = $1 if $hat =~ /^"(.+)"$/;
  5187.  
  5188.         #store we have a declaration if the hat hasn't been seen
  5189.         $profile_data->{$profile}{$hat}{'declared'} = 1
  5190.         unless exists($profile_data->{$profile}{$hat}{declared});
  5191.  
  5192.         } elsif (m/^\s*\^(\"??.+?\"??)\s+(flags=\(.+\)\s+)*\{\s*(#.*)?$/) {
  5193.         if ($do_include) {
  5194.         die "include <$file> contains syntax errors.";
  5195.         }
  5196.             # start of embedded hat syntax hat definition
  5197.             # read in and mark as changed so that will be written out in the new
  5198.             # format
  5199.  
  5200.             # if we hit the start of a contained hat when we're not in a profile
  5201.             # something is wrong...
  5202.             if (not $profile) {
  5203.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5204.             }
  5205.  
  5206.             $in_contained_hat = 1;
  5207.  
  5208.             # we hit the start of a hat inside the current profile
  5209.             $hat = $1;
  5210.             my $flags = $3;
  5211.  
  5212.             # strip quotes.
  5213.             $hat = $1 if $hat =~ /^"(.+)"$/;
  5214.  
  5215.             # keep track of profile flags
  5216.         $profile_data->{$profile}{$hat}{flags} = $flags;
  5217.  
  5218.         # we have seen more than a declaration so clear it
  5219.         $profile_data->{$profile}{$hat}{'declared'} = 0;
  5220.             $profile_data->{$profile}{$hat}{allow}{path} = { };
  5221.             $profile_data->{$profile}{$hat}{allow}{netdomain} = { };
  5222.  
  5223.             # store off initial comment if they have one
  5224.             $profile_data->{$profile}{$hat}{initial_comment} = $initial_comment
  5225.               if $initial_comment;
  5226.             $initial_comment = "";
  5227.             #don't mark profile as changed just because it has an embedded
  5228.         #hat.
  5229.             #$changed{$profile} = 1;
  5230.  
  5231.         $filelist{$file}{profiles}{$profile}{$hat} = 1;
  5232.  
  5233.         } elsif (/^\s*\#/) {
  5234.             # we only currently handle initial comments
  5235.             if (not $profile) {
  5236.                 # ignore vim syntax highlighting lines
  5237.                 next if /^\s*\# vim:syntax/;
  5238.                 # ignore Last Modified: lines
  5239.                 next if /^\s*\# Last Modified:/;
  5240.                 if (/^\s*\# REPOSITORY: (\S+) (\S+) (\S+)$/) {
  5241.                     $repo_data = { url => $1, user => $2, id => $3 };
  5242.                 } elsif (/^\s*\# REPOSITORY: NEVERSUBMIT$/) {
  5243.                     $repo_data = { neversubmit => 1 };
  5244.                 } else {
  5245.                   $initial_comment .= "$_\n";
  5246.                 }
  5247.             }
  5248.         } else {
  5249.         # we hit something we don't understand in a profile...
  5250.         die sprintf(gettext('%s contains syntax errors. Line [%s]'), $file, $_) . "\n";
  5251.         }
  5252.     }
  5253.  
  5254.     #
  5255.     # Cleanup : add required hats if not present in the
  5256.     #           parsed profiles
  5257.     #
  5258. if (not $do_include) {
  5259.     for my $hatglob (keys %{$cfg->{required_hats}}) {
  5260.         for my $parsed_profile  ( sort @parsed_profiles )  {
  5261.             if ($parsed_profile =~ /$hatglob/) {
  5262.                 for my $hat (split(/\s+/, $cfg->{required_hats}{$hatglob})) {
  5263.                     unless ($profile_data->{$parsed_profile}{$hat}) {
  5264.                         $profile_data->{$parsed_profile}{$hat} = { };
  5265.                     }
  5266.                 }
  5267.             }
  5268.         }
  5269.     }
  5270.  
  5271. }    # if we're still in a profile when we hit the end of the file, it's bad
  5272.     if ($profile and not $do_include) {
  5273.         die "Reached the end of $file while we were still inside the $profile profile.\n";
  5274.     }
  5275.  
  5276.     return $profile_data;
  5277. }
  5278.  
  5279. sub eliminate_duplicates(@) {
  5280.     my @data =@_;
  5281.  
  5282.     my %set = map { $_ => 1 } @_;
  5283.     @data = keys %set;
  5284.  
  5285.     return @data;
  5286. }
  5287.  
  5288. sub separate_vars($) {
  5289.     my $vs = shift;
  5290.     my @data;
  5291.  
  5292. #    while ($vs =~ /\s*(((\"([^\"]|\\\"))+?\")|\S*)\s*(.*)$/) {
  5293.     while ($vs =~ /\s*((\".+?\")|([^\"]\S+))\s*(.*)$/) {
  5294.     my $tmp = $1;
  5295.     push @data, strip_quotes($tmp);
  5296.     $vs = $4;
  5297.     }
  5298.  
  5299.     return @data;
  5300. }
  5301.  
  5302. sub is_active_profile ($) {
  5303.     my $pname = shift;
  5304.     if ( $sd{$pname} ) {
  5305.         return 1;
  5306.     }  else {
  5307.         return 0;
  5308.     }
  5309. }
  5310.  
  5311. sub store_list_var (\%$$) {
  5312.     my ($vars, $list_var, $value) = @_;
  5313.  
  5314.     my @vlist = (separate_vars($value));
  5315.  
  5316. #       if (exists $profile_data->{$profile}{$hat}{lvar}{$list_var}) {
  5317. #           @vlist = (@vlist, @{$profile_data->{$profile}{$hat}{lvar}{$list_var}});
  5318. #       }
  5319. #
  5320. #       @vlist = eliminate_duplicates(@vlist);
  5321. #       $profile_data->{$profile}{$hat}{lvar}{$list_var} = \@vlist;
  5322.  
  5323.     if (exists $vars->{$list_var}) {
  5324.     @vlist = (@vlist, @{$vars->{$list_var}});
  5325.     }
  5326.  
  5327.     @vlist = eliminate_duplicates(@vlist);
  5328.     $vars->{$list_var} = \@vlist;
  5329.  
  5330.  
  5331. }
  5332.  
  5333. sub strip_quotes ($) {
  5334.     my $data = shift;
  5335.     $data = $1 if $data =~ /^\"(.*)\"$/;
  5336.     return $data;
  5337. }
  5338.  
  5339. sub quote_if_needed ($) {
  5340.     my $data = shift;
  5341.     $data = "\"$data\"" if $data =~ /\s/;
  5342.  
  5343.     return $data;
  5344. }
  5345.  
  5346. sub escape ($) {
  5347.     my $dangerous = shift;
  5348.  
  5349.     $dangerous = strip_quotes($dangerous);
  5350.  
  5351.     $dangerous =~ s/((?<!\\))"/$1\\"/g;
  5352.     if ($dangerous =~ m/(\s|^$|")/) {
  5353.         $dangerous = "\"$dangerous\"";
  5354.     }
  5355.  
  5356.     return $dangerous;
  5357. }
  5358.  
  5359. sub writeheader ($$$$$) {
  5360.     my ($profile_data, $depth, $name, $embedded_hat, $write_flags) = @_;
  5361.  
  5362.     my $pre = '  ' x $depth;
  5363.     my @data;
  5364.     # deal with whitespace in profile names...
  5365.     $name = quote_if_needed($name);
  5366.  
  5367.     $name = "profile $name" if ((!$embedded_hat && $name =~ /^[^\/]|^"[^\/]/)
  5368.                 || ($embedded_hat && $name =~/^[^^]/));
  5369.  
  5370.     #push @data, "#include <tunables/global>" unless ( $is_hat );
  5371.     if ($write_flags and  $profile_data->{flags}) {
  5372.         push @data, "${pre}$name flags=($profile_data->{flags}) {";
  5373.     } else {
  5374.         push @data, "${pre}$name {";
  5375.     }
  5376.  
  5377.     return @data;
  5378. }
  5379.  
  5380. sub qin_trans ($) {
  5381.     my $value = shift;
  5382.     return quote_if_needed($value);
  5383. }
  5384.  
  5385. sub write_single ($$$$$$) {
  5386.     my ($profile_data, $depth, $allow, $name, $prefix, $tail) = @_;
  5387.     my $ref;
  5388.     my @data;
  5389.  
  5390.     if ($allow) {
  5391.     $ref = $profile_data->{$allow};
  5392.     if ($allow eq 'deny') {
  5393.         $allow .= " ";
  5394.     } else {
  5395.         $allow = "";
  5396.     }
  5397.     } else {
  5398.     $ref = $profile_data;
  5399.     $allow = "";
  5400.     }
  5401.  
  5402.     my $pre = "  " x $depth;
  5403.  
  5404.  
  5405.     # dump out the data
  5406.     if (exists $ref->{$name}) {
  5407.         for my $key (sort keys %{$ref->{$name}}) {
  5408.         my $qkey = quote_if_needed($key);
  5409.         push @data, "${pre}${allow}${prefix}${qkey}${tail}";
  5410.         }
  5411.         push @data, "" if keys %{$ref->{$name}};
  5412.     }
  5413.  
  5414.     return @data;
  5415. }
  5416.  
  5417. sub write_pair ($$$$$$$$) {
  5418.     my ($profile_data, $depth, $allow, $name, $prefix, $sep, $tail, $fn) = @_;
  5419.     my $ref;
  5420.     my @data;
  5421.  
  5422.     if ($allow) {
  5423.     $ref = $profile_data->{$allow};
  5424.     if ($allow eq 'deny') {
  5425.         $allow .= " ";
  5426.     } else {
  5427.         $allow = "";
  5428.     }
  5429.     } else {
  5430.     $ref = $profile_data;
  5431.     $allow = "";
  5432.     }
  5433.  
  5434.     my $pre = "  " x $depth;
  5435.  
  5436.     # dump out the data
  5437.     if (exists $ref->{$name}) {
  5438.         for my $key (sort keys %{$ref->{$name}}) {
  5439.         my $value = &{$fn}($ref->{$name}{$key});
  5440.             push @data, "${pre}${allow}${prefix}${key}${sep}${value}${tail}";
  5441.         }
  5442.         push @data, "" if keys %{$ref->{$name}};
  5443.     }
  5444.  
  5445.     return @data;
  5446. }
  5447.  
  5448. sub writeincludes ($$) {
  5449.     my ($prof_data, $depth) = @_;
  5450.  
  5451.     return write_single($prof_data, $depth,'', 'include', "#include <", ">");
  5452. }
  5453.  
  5454. sub writechange_profile ($$) {
  5455.     my ($prof_data, $depth) = @_;
  5456.  
  5457.     return write_single($prof_data, $depth, '', 'change_profile', "change_profile -> ", ",");
  5458. }
  5459.  
  5460. sub writealiases ($$) {
  5461.     my ($prof_data, $depth) = @_;
  5462.  
  5463.     return write_pair($prof_data, $depth, '', 'alias', "alias ", " -> ", ",", \&qin_trans);
  5464. }
  5465.  
  5466. sub writerlimits ($$) {
  5467.     my ($prof_data, $depth) = @_;
  5468.  
  5469.     return write_pair($prof_data, $depth, '', 'rlimit', "set rlimit ", " <= ", ",", \&qin_trans);
  5470. }
  5471.  
  5472. # take a list references and process it
  5473. sub var_transform($) {
  5474.     my $ref = shift;
  5475.     my @in = @{$ref};
  5476.     my @data;
  5477.  
  5478.     foreach my $value (@in) {
  5479.     push @data, quote_if_needed($value);
  5480.     }
  5481.  
  5482.     return join " ", @data;
  5483. }
  5484.  
  5485. sub writelistvars ($$) {
  5486.     my ($prof_data, $depth) = @_;
  5487.  
  5488.     return write_pair($prof_data, $depth, '', 'lvar', "", " = ", ",", \&var_transform);
  5489. }
  5490.  
  5491. sub writecap_rules ($$$) {
  5492.     my ($profile_data, $depth, $allow) = @_;
  5493.  
  5494.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5495.     my $pre = "  " x $depth;
  5496.  
  5497.     my @data;
  5498.     if (exists $profile_data->{$allow}{capability}) {
  5499.         for my $cap (sort keys %{$profile_data->{$allow}{capability}}) {
  5500.         my $audit = ($profile_data->{$allow}{capability}{$cap}{audit}) ? 'audit ' : '';
  5501.         if ($profile_data->{$allow}{capability}{$cap}{set}) {
  5502.         push @data, "${pre}${audit}${allowstr}capability ${cap},";
  5503.         }
  5504.         }
  5505.     push @data, "";
  5506.     }
  5507.  
  5508.     return @data;
  5509. }
  5510.  
  5511. sub writecapabilities ($$) {
  5512.     my ($prof_data, $depth) = @_;
  5513.     my @data;
  5514.     push @data, write_single($prof_data, $depth, '', 'set_capability', "set capability ", ",");
  5515.     push @data, writecap_rules($prof_data, $depth, 'deny');
  5516.     push @data, writecap_rules($prof_data, $depth, 'allow');
  5517.     return @data;
  5518. }
  5519.  
  5520. sub writenet_rules ($$$) {
  5521.     my ($profile_data, $depth, $allow) = @_;
  5522.  
  5523.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5524.  
  5525.     my $pre = "  " x $depth;
  5526.     my $audit = "";
  5527.  
  5528.     my @data;
  5529.     # dump out the netdomain entries...
  5530.     if (exists $profile_data->{$allow}{netdomain}) {
  5531.         if ( $profile_data->{$allow}{netdomain}{rule} &&
  5532.              $profile_data->{$allow}{netdomain}{rule} eq 'all') {
  5533.         $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{all};
  5534.             push @data, "${pre}${audit}network,";
  5535.         } else {
  5536.             for my $fam (sort keys %{$profile_data->{$allow}{netdomain}{rule}}) {
  5537.                 if ( $profile_data->{$allow}{netdomain}{rule}{$fam} == 1 ) {
  5538.             $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{$fam};
  5539.                     push @data, "${pre}${audit}${allowstr}network $fam,";
  5540.                 } else {
  5541.                     for my $type 
  5542.                         (sort keys %{$profile_data->{$allow}{netdomain}{rule}{$fam}}) {
  5543.                 $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{$fam}{$type};
  5544.                 push @data, "${pre}${audit}${allowstr}network $fam $type,";
  5545.                     }
  5546.                 }
  5547.             }
  5548.         }
  5549.         push @data, "" if %{$profile_data->{$allow}{netdomain}};
  5550.     }
  5551.     return @data;
  5552.  
  5553. }
  5554.  
  5555. sub writenetdomain ($$) {
  5556.     my ($prof_data, $depth) = @_;
  5557.     my @data;
  5558.  
  5559.     push @data, writenet_rules($prof_data, $depth, 'deny');
  5560.     push @data, writenet_rules($prof_data, $depth, 'allow');
  5561.  
  5562.     return @data;
  5563. }
  5564.  
  5565. sub writelink_rules ($$$) {
  5566.     my ($profile_data, $depth, $allow) = @_;
  5567.  
  5568.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5569.     my $pre = "  " x $depth;
  5570.  
  5571.     my @data;
  5572.     if (exists $profile_data->{$allow}{link}) {
  5573.         for my $path (sort keys %{$profile_data->{$allow}{link}}) {
  5574.             my $to = $profile_data->{$allow}{link}{$path}{to};
  5575.         my $subset = ($profile_data->{$allow}{link}{$path}{mode} & $AA_LINK_SUBSET) ? 'subset ' : '';
  5576.         my $audit = ($profile_data->{$allow}{link}{$path}{audit}) ? 'audit ' : '';
  5577.             # deal with whitespace in path names
  5578.             $path = quote_if_needed($path);
  5579.         $to = quote_if_needed($to);
  5580.         push @data, "${pre}${audit}${allowstr}link ${subset}${path} -> ${to},";
  5581.         }
  5582.     push @data, "";
  5583.     }
  5584.  
  5585.     return @data;
  5586. }
  5587.  
  5588. sub writelinks ($$) {
  5589.     my ($profile_data, $depth) = @_;
  5590.     my @data;
  5591.  
  5592.     push @data, writelink_rules($profile_data, $depth, 'deny');
  5593.     push @data, writelink_rules($profile_data, $depth, 'allow');
  5594.  
  5595.     return @data;
  5596. }
  5597.  
  5598. sub writepath_rules ($$$) {
  5599.     my ($profile_data, $depth, $allow) = @_;
  5600.  
  5601.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5602.     my $pre = "  " x $depth;
  5603.  
  5604.     my @data;
  5605.     if (exists $profile_data->{$allow}{path}) {
  5606.         for my $path (sort keys %{$profile_data->{$allow}{path}}) {
  5607.             my $mode = $profile_data->{$allow}{path}{$path}{mode};
  5608.             my $audit = $profile_data->{$allow}{path}{$path}{audit};
  5609.         my $tail = "";
  5610.         $tail = " -> " . $profile_data->{$allow}{path}{$path}{to} if ($profile_data->{$allow}{path}{$path}{to});
  5611.         my ($user, $other) = split_mode($mode);
  5612.         if ($user & ~$other) {
  5613.         $user = $user & ~$other;
  5614.         $mode = $other;
  5615.  
  5616.         if ($user & $audit) {
  5617.             my $amode = $user & $audit;
  5618.             my $modestr = mode_to_str_user($amode);
  5619.             my $str = $allowstr;
  5620.             $str .= "owner " if $modestr =~ s/owner //;
  5621.             if ($path =~ /\s/) {
  5622.             push @data, "${pre}audit ${str}\"$path\" ${modestr}${tail},";
  5623.             } else {
  5624.             push @data, "${pre}audit ${str}$path ${modestr}${tail},";
  5625.             }
  5626.             # mask off the bits we have already written
  5627.             $user &= ~$audit;
  5628.         }
  5629.         if ($user) {
  5630.             my $modestr = mode_to_str_user($user & ~$audit);
  5631.             my $str = $allowstr;
  5632.             $str .= "owner " if $modestr =~ s/owner //;
  5633.  
  5634.             # deal with whitespace in path names
  5635.             if ($path =~ /\s/) {
  5636.             push @data, "${pre}${str}\"$path\" ${modestr}${tail},";
  5637.             } else {
  5638.             push @data, "${pre}${str}$path ${modestr}${tail},";
  5639.             }
  5640.         }
  5641.         if ($mode & $audit) {
  5642.             my $amode = $mode & $audit;
  5643.             my $modestr = mode_to_str_user($amode);
  5644.             my $str = $allowstr;
  5645.             $str .= "owner " if $modestr =~ s/owner //;
  5646.             if ($path =~ /\s/) {
  5647.             push @data, "${pre}audit ${str}\"$path\" ${modestr}${tail},";
  5648.             } else {
  5649.             push @data, "${pre}audit ${str}$path ${modestr}${tail},";
  5650.             }
  5651.             # mask off the bits we have already written
  5652.             $mode &= ~$audit;
  5653.         }
  5654.         if ($mode) {
  5655.             my $modestr = mode_to_str_user($mode & ~$audit);
  5656.             my $str = $allowstr;
  5657.             $str .= "owner " if $modestr =~ s/owner //;
  5658.             # deal with whitespace in path names
  5659.             if ($path =~ /\s/) {
  5660.             push @data, "${pre}${str}\"$path\" ${modestr}${tail},";
  5661.             } else {
  5662.             push @data, "${pre}${str}$path ${modestr}${tail},";
  5663.             }
  5664.         }
  5665.         } else {
  5666.         if ($mode & $audit) {
  5667.             my $amode = $mode & $audit;
  5668.             my $modestr = mode_to_str_user($amode);
  5669.             my $str = $allowstr;
  5670.             $str .= "owner " if $modestr =~ s/owner //;
  5671.             if ($path =~ /\s/) {
  5672.             push @data, "${pre}audit ${str}\"$path\" ${modestr}${tail},";
  5673.             } else {
  5674.             push @data, "${pre}audit ${str}$path ${modestr}${tail},";
  5675.             }
  5676.             # mask off the bits we have already written
  5677.             $mode &= ~$audit;
  5678.         }
  5679.         if ($mode) {
  5680.             my $modestr = mode_to_str_user($mode & ~$audit);
  5681.             my $str = $allowstr;
  5682.             $str .= "owner " if $modestr =~ s/owner //;
  5683.             # deal with whitespace in path names
  5684.             if ($path =~ /\s/) {
  5685.             push @data, "${pre}${str}\"$path\" ${modestr}${tail},";
  5686.             } else {
  5687.             push @data, "${pre}${str}$path ${modestr}${tail},";
  5688.             }
  5689.         }
  5690.         }
  5691.         }
  5692.     push @data, "";
  5693.     }
  5694.  
  5695.     return @data;
  5696. }
  5697.  
  5698. sub writepaths ($$) {
  5699.     my ($prof_data, $depth) = @_;
  5700.  
  5701.     my @data;
  5702.     push @data, writepath_rules($prof_data, $depth, 'deny');
  5703.     push @data, writepath_rules($prof_data, $depth, 'allow');
  5704.  
  5705.     return @data;
  5706. }
  5707.  
  5708. sub write_rules ($$) {
  5709.     my ($prof_data, $depth) = @_;
  5710.  
  5711.     my @data;
  5712.     push @data, writealiases($prof_data, $depth);
  5713.     push @data, writelistvars($prof_data, $depth);
  5714.     push @data, writeincludes($prof_data, $depth);
  5715.     push @data, writerlimits($prof_data, $depth);
  5716.     push @data, writecapabilities($prof_data, $depth);
  5717.     push @data, writenetdomain($prof_data, $depth);
  5718.     push @data, writelinks($prof_data, $depth);
  5719.     push @data, writepaths($prof_data, $depth);
  5720.     push @data, writechange_profile($prof_data, $depth);
  5721.  
  5722.     return @data;
  5723. }
  5724.  
  5725. sub writepiece ($$$$$);
  5726. sub writepiece ($$$$$) {
  5727.     my ($profile_data, $depth, $name, $nhat, $write_flags) = @_;
  5728.  
  5729.     my $pre = '  ' x $depth;
  5730.     my @data;
  5731.     my $wname;
  5732.     my $inhat = 0;
  5733.     if ($name eq $nhat) {
  5734.     $wname = $name;
  5735.     } else {
  5736.     $wname = "$name//$nhat";
  5737.     $name = $nhat;
  5738.     $inhat = 1;
  5739.     }
  5740.     push @data, writeheader($profile_data->{$name}, $depth, $wname, 0, $write_flags);
  5741.     push @data, write_rules($profile_data->{$name}, $depth + 1);
  5742.  
  5743.     my $pre2 = '  ' x ($depth + 1);
  5744.     # write external hat declarations
  5745.     for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) {
  5746.     if ($profile_data->{$hat}{declared}) {
  5747.         push @data, "${pre2}^$hat,";
  5748.     }
  5749.     }
  5750.  
  5751.     if (!$inhat) {
  5752.     # write embedded hats
  5753.     for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) {
  5754.         if ((not $profile_data->{$hat}{external}) and
  5755.         (not $profile_data->{$hat}{declared})) {
  5756.         push @data, "";
  5757.         if ($profile_data->{$hat}{profile}) {
  5758.             push @data, map { "$_" } writeheader($profile_data->{$hat},
  5759.                              $depth + 1, $hat,
  5760.                              1, $write_flags);
  5761.         } else {
  5762.             push @data, map { "$_" } writeheader($profile_data->{$hat},
  5763.                              $depth + 1, "^$hat",
  5764.                              1, $write_flags);
  5765.         }
  5766.         push @data, map { "$_" } write_rules($profile_data->{$hat},
  5767.                              $depth + 2);
  5768.         push @data, "${pre2}}";
  5769.         }
  5770.     }
  5771.     push @data, "${pre}}";
  5772.  
  5773.     #write external hats
  5774.     for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) {
  5775.         if (($name eq $nhat) and $profile_data->{$hat}{external}) {
  5776.         push @data, "";
  5777.         push @data, map { "  $_" } writepiece($profile_data, $depth - 1,
  5778.                               $name, $hat, $write_flags);
  5779.         push @data, "  }";
  5780.         }
  5781.     }
  5782.     }
  5783.     return @data;
  5784. }
  5785.  
  5786. sub serialize_profile {
  5787.     my ($profile_data, $name, $options) = @_;
  5788.  
  5789.     my $string = "";
  5790.     my $include_metadata = 0;  # By default don't write out metadata
  5791.     my $include_flags = 1;
  5792.     if ( $options and ref($options) eq "HASH" ) {
  5793.        $include_metadata = 1 if ( defined $options->{METADATA} );
  5794.        $include_flags    = 0 if ( defined $options->{NO_FLAGS} );
  5795.     }
  5796.  
  5797.     if ($include_metadata) {
  5798.         # keep track of when the file was last updated
  5799.         $string .= "# Last Modified: " . localtime(time) . "\n";
  5800.  
  5801.         # print out repository metadata
  5802.         if ($profile_data->{$name}{repo}       &&
  5803.             $profile_data->{$name}{repo}{url}  &&
  5804.             $profile_data->{$name}{repo}{user} &&
  5805.             $profile_data->{$name}{repo}{id}) {
  5806.             my $repo = $profile_data->{$name}{repo};
  5807.             $string .= "# REPOSITORY: $repo->{url} $repo->{user} $repo->{id}\n";
  5808.         } elsif ($profile_data->{$name}{repo}{neversubmit}) {
  5809.             $string .= "# REPOSITORY: NEVERSUBMIT\n";
  5810.         }
  5811.     }
  5812.  
  5813.     # print out initial comment
  5814.     if ($profile_data->{$name}{initial_comment}) {
  5815.         my $comment = $profile_data->{$name}{initial_comment};
  5816.         $comment =~ s/\\n/\n/g;
  5817.         $string .= "$comment\n";
  5818.     }
  5819.  
  5820.     #bleah this is stupid the data structure needs to be reworked
  5821.     my $filename = getprofilefilename($name);
  5822.     my @data;
  5823.     if ($filelist{$filename}) {
  5824.     push @data, writealiases($filelist{$filename}, 0);
  5825.     push @data, writelistvars($filelist{$filename}, 0);
  5826.     push @data, writeincludes($filelist{$filename}, 0);
  5827.     }
  5828.  
  5829.  
  5830. # XXX - FIXME
  5831. #
  5832. #  # dump variables defined in this file
  5833. #  if ($variables{$filename}) {
  5834. #    for my $var (sort keys %{$variables{$filename}}) {
  5835. #      if ($var =~ m/^@/) {
  5836. #        my @values = sort @{$variables{$filename}{$var}};
  5837. #        @values = map { escape($_) } @values;
  5838. #        my $values = join (" ", @values);
  5839. #        print SDPROF "$var = ";
  5840. #        print SDPROF $values;
  5841. #      } elsif ($var =~ m/^\$/) {
  5842. #        print SDPROF "$var = ";
  5843. #        print SDPROF ${$variables{$filename}{$var}};
  5844. #      } elsif ($var =~ m/^\#/) {
  5845. #        my $inc = $var;
  5846. #        $inc =~ s/^\#//;
  5847. #        print SDPROF "#include <$inc>";
  5848. #      }
  5849. #      print SDPROF "\n";
  5850. #    }
  5851. #  }
  5852.  
  5853.     push @data, writepiece($profile_data, 0, $name, $name, $include_flags);
  5854.     $string .= join("\n", @data);
  5855.  
  5856.     return "$string\n";
  5857. }
  5858.  
  5859. sub writeprofile_ui_feedback ($) {
  5860.     my $profile = shift;
  5861.     UI_Info(sprintf(gettext('Writing updated profile for %s.'), $profile));
  5862.     writeprofile($profile);
  5863. }
  5864.  
  5865. sub writeprofile ($) {
  5866.     my ($profile) = shift;
  5867.  
  5868.     my $filename = $sd{$profile}{$profile}{filename} || getprofilefilename($profile);
  5869.  
  5870.     open(SDPROF, ">$filename") or
  5871.       fatal_error "Can't write new AppArmor profile $filename: $!";
  5872.     my $serialize_opts = { };
  5873.     $serialize_opts->{METADATA} = 1;
  5874.  
  5875.     #make sure to write out all the profiles in the file
  5876.     my $profile_string = serialize_profile($sd{$profile}, $profile, $serialize_opts);
  5877.     print SDPROF $profile_string;
  5878.     close(SDPROF);
  5879.  
  5880.     # mark the profile as up-to-date
  5881.     delete $changed{$profile};
  5882.     $original_sd{$profile} = dclone($sd{$profile});
  5883. }
  5884.  
  5885. sub getprofileflags {
  5886.     my $filename = shift;
  5887.  
  5888.     my $flags = "enforce";
  5889.  
  5890.     if (open(PROFILE, "$filename")) {
  5891.         while (<PROFILE>) {
  5892.             if (m/^\s*\/\S+\s+flags=\((.+)\)\s+{\s*$/) {
  5893.                 $flags = $1;
  5894.                 close(PROFILE);
  5895.                 return $flags;
  5896.             }
  5897.         }
  5898.         close(PROFILE);
  5899.     }
  5900.  
  5901.     return $flags;
  5902. }
  5903.  
  5904.  
  5905. sub matchliteral {
  5906.     my ($sd_regexp, $literal) = @_;
  5907.  
  5908.     my $p_regexp = convert_regexp($sd_regexp);
  5909.  
  5910.     # check the log entry against our converted regexp...
  5911.     my $matches = eval { $literal =~ /^$p_regexp$/; };
  5912.  
  5913.     # doesn't match if we've got a broken regexp
  5914.     return undef if $@;
  5915.  
  5916.     return $matches;
  5917. }
  5918.  
  5919. # test if profile has exec rule for $exec_target
  5920. sub profile_known_exec (\%$$) {
  5921.     my ($profile, $type, $exec_target) = @_;
  5922.     if ( $type eq "exec" ) {
  5923.         my ($cm, $am, @m);
  5924.  
  5925.         # test denies first
  5926.         ($cm, $am, @m) = rematchfrag($profile, 'deny', $exec_target);
  5927.     if ($cm & $AA_MAY_EXEC) {
  5928.         return -1;
  5929.     }
  5930.         ($cm, $am, @m) = match_prof_incs_to_path($profile, 'deny', $exec_target);
  5931.     if ($cm & $AA_MAY_EXEC) {
  5932.         return -1;
  5933.     }
  5934.  
  5935.     # now test the generally longer allow lists
  5936.         ($cm, $am, @m) = rematchfrag($profile, 'allow', $exec_target);
  5937.     if ($cm & $AA_MAY_EXEC) {
  5938.         return 1;
  5939.     }
  5940.  
  5941.         ($cm, $am, @m) = match_prof_incs_to_path($profile, 'allow', $exec_target);
  5942.     if ($cm & $AA_MAY_EXEC) {
  5943.         return 1;
  5944.     }
  5945.     }
  5946.     return 0;
  5947. }
  5948.  
  5949. sub profile_known_capability (\%$) {
  5950.     my ($profile, $capname) = @_;
  5951.  
  5952.     return -1 if $profile->{deny}{capability}{$capname}{set};
  5953.     return 1 if $profile->{allow}{capability}{$capname}{set};
  5954.     for my $incname ( keys %{$profile->{include}} ) {
  5955.     return -1 if $include{$incname}{$incname}{deny}{capability}{$capname}{set};
  5956.     return 1 if $include{$incname}{$incname}{allow}{capability}{$capname}{set};
  5957.     }
  5958.     return 0;
  5959. }
  5960.  
  5961. sub profile_known_network (\%$$) {
  5962.     my ($profile, $family, $sock_type) = @_;
  5963.  
  5964.     return -1 if netrules_access_check( $profile->{deny}{netdomain},
  5965.                                        $family, $sock_type);
  5966.     return 1 if netrules_access_check( $profile->{allow}{netdomain},
  5967.                                        $family, $sock_type);
  5968.  
  5969.     for my $incname ( keys %{$profile->{include}} ) {
  5970.         return -1 if netrules_access_check($include{$incname}{$incname}{deny}{netdomain},
  5971.                                         $family, $sock_type);
  5972.         return 1 if netrules_access_check($include{$incname}{$incname}{allow}{netdomain},
  5973.                       $family, $sock_type);
  5974.     }
  5975.  
  5976.     return 0;
  5977. }
  5978.  
  5979. sub netrules_access_check ($$$) {
  5980.     my ($netrules, $family, $sock_type) = @_;
  5981.     return 0 if ( not defined $netrules );
  5982.     my %netrules        = %$netrules;;
  5983.     my $all_net         = defined $netrules{rule}{all};
  5984.     my $all_net_family  = defined $netrules{rule}{$family} && $netrules{rule}{$family} == 1;
  5985.     my $net_family_sock = defined $netrules{rule}{$family} &&
  5986.                           ref($netrules{rule}{$family}) eq "HASH" &&
  5987.                           defined $netrules{rule}{$family}{$sock_type};
  5988.  
  5989.     if ( $all_net || $all_net_family || $net_family_sock ) {
  5990.         return 1;
  5991.     } else {
  5992.       return 0;
  5993.     }
  5994. }
  5995.  
  5996. sub reload_base($) {
  5997.     my $bin = shift;
  5998.  
  5999.     # don't try to reload profile if AppArmor is not running
  6000.     return unless check_for_subdomain();
  6001.  
  6002.     my $filename = getprofilefilename($bin);
  6003.  
  6004.     system("/bin/cat '$filename' | $parser -I$profiledir -r >/dev/null 2>&1");
  6005. }
  6006.  
  6007. sub reload ($) {
  6008.     my $bin = shift;
  6009.  
  6010.     # don't reload the profile if the corresponding executable doesn't exist
  6011.     my $fqdbin = findexecutable($bin) or return;
  6012.  
  6013.     return reload_base($fqdbin);
  6014. }
  6015.  
  6016. sub read_include_from_file {
  6017.     my $which = shift;
  6018.  
  6019.     my $data;
  6020.     if (open(INCLUDE, "$profiledir/$which")) {
  6021.         local $/;
  6022.         $data = <INCLUDE>;
  6023.         close(INCLUDE);
  6024.     }
  6025.  
  6026.     return $data;
  6027. }
  6028.  
  6029. sub get_include_data {
  6030.     my $which = shift;
  6031.  
  6032.     my $data = read_include_from_file($which);
  6033.     unless($data) {
  6034.         fatal_error "Can't find include file $which: $!";
  6035.     }
  6036.     return $data;
  6037. }
  6038.  
  6039. sub loadinclude {
  6040.     my $which = shift;
  6041.  
  6042.     # don't bother loading it again if we already have
  6043.     return 0 if $include{$which}{$which};
  6044.  
  6045.     my @loadincludes = ($which);
  6046.     while (my $incfile = shift @loadincludes) {
  6047.  
  6048.         my $data = get_include_data($incfile);
  6049.     my $incdata = parse_profile_data($data, $incfile, 1);
  6050.     if ($incdata) {
  6051.                     attach_profile_data(\%include, $incdata);
  6052.     }
  6053.     }
  6054.     return 0;
  6055. }
  6056.  
  6057. sub rematchfrag ($$$) {
  6058.     my ($frag, $allow, $path) = @_;
  6059.  
  6060.     my $combinedmode = 0;
  6061.     my $combinedaudit = 0;
  6062.     my @matches;
  6063.  
  6064.     for my $entry (keys %{ $frag->{$allow}{path} }) {
  6065.  
  6066.         my $regexp = convert_regexp($entry);
  6067.  
  6068.         # check the log entry against our converted regexp...
  6069.         if ($path =~ /^$regexp$/) {
  6070.  
  6071.             # regexp matches, add it's mode to the list to check against
  6072.             $combinedmode |= $frag->{$allow}{path}{$entry}{mode};
  6073.             $combinedaudit |= $frag->{$allow}{path}{$entry}{audit};
  6074.             push @matches, $entry;
  6075.         }
  6076.     }
  6077.  
  6078.     return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6079. }
  6080.  
  6081. sub match_include_to_path ($$$) {
  6082.     my ($incname, $allow, $path) = @_;
  6083.  
  6084.     my $combinedmode = 0;
  6085.     my $combinedaudit = 0;
  6086.     my @matches;
  6087.  
  6088.     my @includelist = ( $incname );
  6089.     while (my $incfile = shift @includelist) {
  6090.         my $ret = eval { loadinclude($incfile); };
  6091.         if ($@) { fatal_error $@; }
  6092.         my ($cm, $am, @m) = rematchfrag($include{$incfile}{$incfile}, $allow, $path);
  6093.         if ($cm) {
  6094.             $combinedmode |= $cm;
  6095.         $combinedaudit |= $am;
  6096.             push @matches, @m;
  6097.         }
  6098.  
  6099.         # check if a literal version is in the current include fragment
  6100.         if ($include{$incfile}{$incfile}{$allow}{path}{$path}) {
  6101.             $combinedmode |= $include{$incfile}{$incfile}{$allow}{path}{$path}{mode};
  6102.             $combinedaudit |= $include{$incfile}{$incfile}{$allow}{path}{$path}{audit};
  6103.         }
  6104.  
  6105.         # if this fragment includes others, check them too
  6106.         if (keys %{ $include{$incfile}{$incfile}{include} }) {
  6107.             push @includelist, keys %{ $include{$incfile}{$incfile}{include} };
  6108.         }
  6109.     }
  6110.  
  6111.     return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6112. }
  6113.  
  6114. sub match_prof_incs_to_path ($$$) {
  6115.     my ($frag, $allow, $path) = @_;
  6116.  
  6117.     my $combinedmode = 0;
  6118.     my $combinedaudit = 0;
  6119.     my @matches;
  6120.  
  6121.     # scan the include fragments for this profile looking for matches
  6122.     my @includelist = keys %{ $frag->{include} };
  6123.     while (my $include = shift @includelist) {
  6124.     my ($cm, $am, @m) = match_include_to_path($include, $allow, $path);
  6125.         if ($cm) {
  6126.             $combinedmode |= $cm;
  6127.             $combinedaudit |= $am;
  6128.             push @matches, @m;
  6129.         }
  6130.     }
  6131.  
  6132.     return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6133. }
  6134.  
  6135. #find includes that match the path to suggest
  6136. sub suggest_incs_for_path {
  6137.     my ($incname, $path, $allow) = @_;
  6138.  
  6139.  
  6140.     my $combinedmode = 0;
  6141.     my $combinedaudit = 0;
  6142.     my @matches;
  6143.  
  6144.     # scan the include fragments looking for matches
  6145.     my @includelist = ($incname);
  6146.     while (my $include = shift @includelist) {
  6147.         my ($cm, $am, @m) = rematchfrag($include{$include}{$include}, 'allow', $path);
  6148.         if ($cm) {
  6149.             $combinedmode |= $cm;
  6150.             $combinedaudit |= $am;
  6151.             push @matches, @m;
  6152.         }
  6153.  
  6154.         # check if a literal version is in the current include fragment
  6155.         if ($include{$include}{$include}{allow}{path}{$path}) {
  6156.             $combinedmode |= $include{$include}{$include}{allow}{path}{$path}{mode};
  6157.             $combinedaudit |= $include{$include}{$include}{allow}{path}{$path}{audit};
  6158.         }
  6159.  
  6160.         # if this fragment includes others, check them too
  6161.         if (keys %{ $include{$include}{$include}{include} }) {
  6162.             push @includelist, keys %{ $include{$include}{$include}{include} };
  6163.         }
  6164.     }
  6165.  
  6166.     if ($combinedmode) {
  6167.         return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6168.     } else {
  6169.         return;
  6170.     }
  6171. }
  6172.  
  6173. sub check_qualifiers {
  6174.     my $program = shift;
  6175.  
  6176.     if ($cfg->{qualifiers}{$program}) {
  6177.         unless($cfg->{qualifiers}{$program} =~ /p/) {
  6178.             fatal_error(sprintf(gettext("\%s is currently marked as a program that should not have it's own profile.  Usually, programs are marked this way if creating a profile for them is likely to break the rest of the system.  If you know what you're doing and are certain you want to create a profile for this program, edit the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf."), $program));
  6179.         }
  6180.     }
  6181. }
  6182.  
  6183. sub loadincludes {
  6184.     if (opendir(SDDIR, $profiledir)) {
  6185.         my @incdirs = grep { (!/^\./) && (-d "$profiledir/$_") } readdir(SDDIR);
  6186.         close(SDDIR);
  6187.  
  6188.         while (my $id = shift @incdirs) {
  6189.             if (opendir(SDDIR, "$profiledir/$id")) {
  6190.                 for my $path (readdir(SDDIR)) {
  6191.                     chomp($path);
  6192.                     next if isSkippableFile($path);
  6193.                     if (-f "$profiledir/$id/$path") {
  6194.                         my $file = "$id/$path";
  6195.                         $file =~ s/$profiledir\///;
  6196.                         my $ret = eval { loadinclude($file); };
  6197.                         if ($@) { fatal_error $@; }
  6198.                     } elsif (-d "$id/$path") {
  6199.                         push @incdirs, "$id/$path";
  6200.                     }
  6201.                 }
  6202.                 closedir(SDDIR);
  6203.             }
  6204.         }
  6205.     }
  6206. }
  6207.  
  6208. sub globcommon ($) {
  6209.     my $path = shift;
  6210.  
  6211.     my @globs;
  6212.  
  6213.     # glob library versions in both foo-5.6.so and baz.so.9.2 form
  6214.     if ($path =~ m/[\d\.]+\.so$/ || $path =~ m/\.so\.[\d\.]+$/) {
  6215.         my $libpath = $path;
  6216.         $libpath =~ s/[\d\.]+\.so$/*.so/;
  6217.         $libpath =~ s/\.so\.[\d\.]+$/.so.*/;
  6218.         push @globs, $libpath if $libpath ne $path;
  6219.     }
  6220.  
  6221.     for my $glob (keys %{$cfg->{globs}}) {
  6222.         if ($path =~ /$glob/) {
  6223.             my $globbedpath = $path;
  6224.             $globbedpath =~ s/$glob/$cfg->{globs}{$glob}/g;
  6225.             push @globs, $globbedpath if $globbedpath ne $path;
  6226.         }
  6227.     }
  6228.  
  6229.     if (wantarray) {
  6230.         return sort { length($b) <=> length($a) } uniq(@globs);
  6231.     } else {
  6232.         my @list = sort { length($b) <=> length($a) } uniq(@globs);
  6233.         return $list[$#list];
  6234.     }
  6235. }
  6236.  
  6237. # this is an ugly, nasty function that attempts to see if one regexp
  6238. # is a subset of another regexp
  6239. sub matchregexp ($$) {
  6240.     my ($new, $old) = @_;
  6241.  
  6242.     # bail out if old pattern has {foo,bar,baz} stuff in it
  6243.     return undef if $old =~ /\{.*(\,.*)*\}/;
  6244.  
  6245.     # are there any regexps at all in the old pattern?
  6246.     if ($old =~ /\[.+\]/ or $old =~ /\*/ or $old =~ /\?/) {
  6247.  
  6248.         # convert {foo,baz} to (foo|baz)
  6249.         $new =~ y/\{\}\,/\(\)\|/ if $new =~ /\{.*\,.*\}/;
  6250.  
  6251.         # \001 == SD_GLOB_RECURSIVE
  6252.         # \002 == SD_GLOB_SIBLING
  6253.  
  6254.         $new =~ s/\*\*/\001/g;
  6255.         $new =~ s/\*/\002/g;
  6256.  
  6257.         $old =~ s/\*\*/\001/g;
  6258.         $old =~ s/\*/\002/g;
  6259.  
  6260.         # strip common prefix
  6261.         my $prefix = commonprefix($new, $old);
  6262.         if ($prefix) {
  6263.  
  6264.             # make sure we don't accidentally gobble up a trailing * or **
  6265.             $prefix =~ s/(\001|\002)$//;
  6266.             $new    =~ s/^$prefix//;
  6267.             $old    =~ s/^$prefix//;
  6268.         }
  6269.  
  6270.         # strip common suffix
  6271.         my $suffix = commonsuffix($new, $old);
  6272.         if ($suffix) {
  6273.  
  6274.             # make sure we don't accidentally gobble up a leading * or **
  6275.             $suffix =~ s/^(\001|\002)//;
  6276.             $new    =~ s/$suffix$//;
  6277.             $old    =~ s/$suffix$//;
  6278.         }
  6279.  
  6280.         # if we boiled the differences down to a ** in the new entry, it matches
  6281.         # whatever's in the old entry
  6282.         return 1 if $new eq "\001";
  6283.  
  6284.         # if we've paired things down to a * in new, old matches if there are no
  6285.         # slashes left in the path
  6286.         return 1 if ($new eq "\002" && $old =~ /^[^\/]+$/);
  6287.  
  6288.         # we'll bail out if we have more globs in the old version
  6289.         return undef if $old =~ /\001|\002/;
  6290.  
  6291.         # see if we can match * globs in new against literal elements in old
  6292.         $new =~ s/\002/[^\/]*/g;
  6293.  
  6294.         return 1 if $old =~ /^$new$/;
  6295.  
  6296.     } else {
  6297.  
  6298.         my $new_regexp = convert_regexp($new);
  6299.  
  6300.         # check the log entry against our converted regexp...
  6301.         return 1 if $old =~ /^$new_regexp$/;
  6302.  
  6303.     }
  6304.  
  6305.     return undef;
  6306. }
  6307.  
  6308. sub combine_name($$) { return ($_[0] eq $_[1]) ? $_[0] : "$_[0]^$_[1]"; }
  6309. sub split_name ($) { my ($p, $h) = split(/\^/, $_[0]); $h ||= $p; ($p, $h); }
  6310.  
  6311. ##########################
  6312. #
  6313. # prompt_user($headers, $functions, $default, $options, $selected);
  6314. #
  6315. # $headers:
  6316. #   a required arrayref made up of "key, value" pairs in the order you'd
  6317. #   like them displayed to user
  6318. #
  6319. # $functions:
  6320. #   a required arrayref of the different options to display at the bottom
  6321. #   of the prompt like "(A)llow", "(D)eny", and "Ba(c)on".  the character
  6322. #   contained by ( and ) will be used as the key to select the specified
  6323. #   option.
  6324. #
  6325. # $default:
  6326. #   a required character which is the default "key" to enter when they
  6327. #   just hit enter
  6328. #
  6329. # $options:
  6330. #   an optional arrayref of the choices like the glob suggestions to be
  6331. #   presented to the user
  6332. #
  6333. # $selected:
  6334. #   specifies which option is currently selected
  6335. #
  6336. # when prompt_user() is called without an $options list, it returns a
  6337. # single value which is the key for the specified "function".
  6338. #
  6339. # when prompt_user() is called with an $options list, it returns an array
  6340. # of two elements, the key for the specified function as well as which
  6341. # option was currently selected
  6342. #######################################################################
  6343.  
  6344. sub Text_PromptUser ($) {
  6345.     my $question = shift;
  6346.  
  6347.     my $title     = $question->{title};
  6348.     my $explanation = $question->{explanation};
  6349.  
  6350.     my @headers   = (@{ $question->{headers} });
  6351.     my @functions = (@{ $question->{functions} });
  6352.  
  6353.     my $default  = $question->{default};
  6354.     my $options  = $question->{options};
  6355.     my $selected = $question->{selected} || 0;
  6356.  
  6357.     my $helptext = $question->{helptext};
  6358.  
  6359.     push @functions, "CMD_HELP" if $helptext;
  6360.  
  6361.     my %keys;
  6362.     my @menu_items;
  6363.     for my $cmd (@functions) {
  6364.  
  6365.         # make sure we know about this particular command
  6366.         my $cmdmsg = "PromptUser: " . gettext("Unknown command") . " $cmd";
  6367.         fatal_error $cmdmsg unless $CMDS{$cmd};
  6368.  
  6369.         # grab the localized text to use for the menu for this command
  6370.         my $menutext = gettext($CMDS{$cmd});
  6371.  
  6372.         # figure out what the hotkey for this menu item is
  6373.         my $menumsg = "PromptUser: " .
  6374.                       gettext("Invalid hotkey in") .
  6375.                       " '$menutext'";
  6376.         $menutext =~ /\((\S)\)/ or fatal_error $menumsg;
  6377.  
  6378.         # we want case insensitive comparisons so we'll force things to
  6379.         # lowercase
  6380.         my $key = lc($1);
  6381.  
  6382.         # check if we're already using this hotkey for this prompt
  6383.         my $hotkeymsg = "PromptUser: " .
  6384.                         gettext("Duplicate hotkey for") .
  6385.                         " $cmd: $menutext";
  6386.         fatal_error $hotkeymsg if $keys{$key};
  6387.  
  6388.         # keep track of which command they're picking if they hit this hotkey
  6389.         $keys{$key} = $cmd;
  6390.  
  6391.         if ($default && $default eq $cmd) {
  6392.             $menutext = "[$menutext]";
  6393.         }
  6394.  
  6395.         push @menu_items, $menutext;
  6396.     }
  6397.  
  6398.     # figure out the key for the default option
  6399.     my $default_key;
  6400.     if ($default && $CMDS{$default}) {
  6401.         my $defaulttext = gettext($CMDS{$default});
  6402.  
  6403.         # figure out what the hotkey for this menu item is
  6404.         my $defmsg = "PromptUser: " .
  6405.                       gettext("Invalid hotkey in default item") .
  6406.                       " '$defaulttext'";
  6407.         $defaulttext =~ /\((\S)\)/ or fatal_error $defmsg;
  6408.  
  6409.         # we want case insensitive comparisons so we'll force things to
  6410.         # lowercase
  6411.         $default_key = lc($1);
  6412.  
  6413.         my $defkeymsg = "PromptUser: " .
  6414.                         gettext("Invalid default") .
  6415.                         " $default";
  6416.         fatal_error $defkeymsg unless $keys{$default_key};
  6417.     }
  6418.  
  6419.     my $widest = 0;
  6420.     my @poo    = @headers;
  6421.     while (my $header = shift @poo) {
  6422.         my $value = shift @poo;
  6423.         $widest = length($header) if length($header) > $widest;
  6424.     }
  6425.     $widest++;
  6426.  
  6427.     my $format = '%-' . $widest . "s \%s\n";
  6428.  
  6429.     my $function_regexp = '^(';
  6430.     $function_regexp .= join("|", keys %keys);
  6431.     $function_regexp .= '|\d' if $options;
  6432.     $function_regexp .= ')$';
  6433.  
  6434.     my $ans = "XXXINVALIDXXX";
  6435.     while ($ans !~ /$function_regexp/i) {
  6436.         # build up the prompt...
  6437.         my $prompt = "\n";
  6438.  
  6439.         $prompt .= "= $title =\n\n" if $title;
  6440.  
  6441.         if (@headers) {
  6442.             my @poo = @headers;
  6443.             while (my $header = shift @poo) {
  6444.                 my $value = shift @poo;
  6445.                 $prompt .= sprintf($format, "$header:", $value);
  6446.             }
  6447.             $prompt .= "\n";
  6448.         }
  6449.  
  6450.         if ($explanation) {
  6451.             $prompt .= "$explanation\n\n";
  6452.         }
  6453.  
  6454.         if ($options) {
  6455.             for (my $i = 0; $options->[$i]; $i++) {
  6456.                 my $f = ($selected == $i) ? ' [%d - %s]' : '  %d - %s ';
  6457.                 $prompt .= sprintf("$f\n", $i + 1, $options->[$i]);
  6458.             }
  6459.             $prompt .= "\n";
  6460.         }
  6461.         $prompt .= join(" / ", @menu_items);
  6462.         print "$prompt\n";
  6463.  
  6464.         # get their input...
  6465.         $ans = lc(getkey());
  6466.  
  6467.         if ($ans) {
  6468.             # handle escape sequences so you can up/down in the list
  6469.             if ($ans eq "up") {
  6470.  
  6471.                 if ($options && ($selected > 0)) {
  6472.                     $selected--;
  6473.                 }
  6474.                 $ans = "XXXINVALIDXXX";
  6475.  
  6476.             } elsif ($ans eq "down") {
  6477.  
  6478.                 if ($options && ($selected < (scalar(@$options) - 1))) {
  6479.                     $selected++;
  6480.                 }
  6481.                 $ans = "XXXINVALIDXXX";
  6482.  
  6483.             } elsif ($keys{$ans} && $keys{$ans} eq "CMD_HELP") {
  6484.  
  6485.                 print "\n$helptext\n";
  6486.                 $ans = "XXXINVALIDXXX";
  6487.  
  6488.             } elsif (ord($ans) == 10) {
  6489.  
  6490.                 # pick the default if they hit return...
  6491.                 $ans = $default_key;
  6492.  
  6493.             } elsif ($options && ($ans =~ /^\d$/)) {
  6494.  
  6495.                 # handle option poo
  6496.                 if ($ans > 0 && $ans <= scalar(@$options)) {
  6497.                     $selected = $ans - 1;
  6498.                 }
  6499.                 $ans = "XXXINVALIDXXX";
  6500.             }
  6501.         }
  6502.  
  6503.         if ($keys{$ans} && $keys{$ans} eq "CMD_HELP") {
  6504.             print "\n$helptext\n";
  6505.             $ans = "again";
  6506.         }
  6507.     }
  6508.  
  6509.     # pull our command back from our hotkey map
  6510.     $ans = $keys{$ans} if $keys{$ans};
  6511.     return ($ans, $selected);
  6512.  
  6513. }
  6514.  
  6515. # Parse event record into key-value pairs
  6516. sub parse_event($) {
  6517.     my %ev = ();
  6518.     my $msg = shift;
  6519.     chomp($msg);
  6520.     my $event = LibAppArmor::parse_record($msg);
  6521.     my ($rmask, $dmask);
  6522.  
  6523.     $ev{'resource'}   = LibAppArmor::aa_log_record::swig_info_get($event);
  6524.     $ev{'active_hat'} = LibAppArmor::aa_log_record::swig_active_hat_get($event);
  6525.     $ev{'sdmode'}     = LibAppArmor::aa_log_record::swig_event_get($event);
  6526.     $ev{'time'}       = LibAppArmor::aa_log_record::swig_epoch_get($event);
  6527.     $ev{'operation'}  = LibAppArmor::aa_log_record::swig_operation_get($event);
  6528.     $ev{'profile'}    = LibAppArmor::aa_log_record::swig_profile_get($event);
  6529.     $ev{'name'}       = LibAppArmor::aa_log_record::swig_name_get($event);
  6530.     $ev{'name2'}      = LibAppArmor::aa_log_record::swig_name2_get($event);
  6531.     $ev{'attr'}       = LibAppArmor::aa_log_record::swig_attribute_get($event);
  6532.     $ev{'parent'}     = LibAppArmor::aa_log_record::swig_parent_get($event);
  6533.     $ev{'pid'}        = LibAppArmor::aa_log_record::swig_pid_get($event);
  6534.     $ev{'task'}        = LibAppArmor::aa_log_record::swig_task_get($event);
  6535.     $ev{'info'}        = LibAppArmor::aa_log_record::swig_info_get($event);
  6536.     $dmask = LibAppArmor::aa_log_record::swig_denied_mask_get($event);
  6537.     $rmask = LibAppArmor::aa_log_record::swig_requested_mask_get($event);
  6538.     $ev{'magic_token'}  =
  6539.        LibAppArmor::aa_log_record::swig_magic_token_get($event);
  6540.  
  6541.     # NetDomain
  6542.     if ( $ev{'operation'} && $ev{'operation'} =~ /socket/ ) {
  6543.         $ev{'family'}    =
  6544.             LibAppArmor::aa_log_record::swig_net_family_get($event);
  6545.         $ev{'protocol'}  =
  6546.             LibAppArmor::aa_log_record::swig_net_protocol_get($event);
  6547.         $ev{'sock_type'} =
  6548.             LibAppArmor::aa_log_record::swig_net_sock_type_get($event);
  6549.     }
  6550.  
  6551.     LibAppArmor::free_record($event);
  6552.  
  6553.     if ($rmask && !validate_log_mode(hide_log_mode($rmask))) {
  6554.         fatal_error(sprintf(gettext('Log contains unknown mode %s.'),
  6555.                             $rmask));
  6556.     }
  6557.  
  6558.     if ($dmask && !validate_log_mode(hide_log_mode($dmask))) {
  6559.         fatal_error(sprintf(gettext('Log contains unknown mode %s.'),
  6560.                     $dmask));
  6561.     }
  6562. #print "str_to_mode deny $dmask = " . str_to_mode($dmask) . "\n" if ($dmask);
  6563. #print "str_to_mode req $rmask = "  . str_to_mode($rmask) . "\n" if ($rmask);
  6564.  
  6565.     my ($mask, $name);
  6566.     ($mask, $name) = log_str_to_mode($ev{profile}, $dmask, $ev{name2});
  6567.     $ev{'denied_mask'} = $mask;
  6568.     $ev{name2} = $name;
  6569.  
  6570.     ($mask, $name) = log_str_to_mode($ev{profile}, $rmask, $ev{name2});
  6571.     $ev{'request_mask'} = $mask;
  6572.     $ev{name2} = $name;
  6573.  
  6574.     if ( ! $ev{'time'} ) { $ev{'time'} = time; }
  6575.  
  6576.     # remove null responses
  6577.     for (keys(%ev)) {
  6578.         if ( ! $ev{$_} || $ev{$_} !~ /\w+/)  { delete($ev{$_}); }
  6579.     }
  6580.  
  6581.     if ( $ev{'sdmode'} ) {
  6582.         #0 = invalid, 1 = error, 2 = AUDIT, 3 = ALLOW/PERMIT,
  6583.         #4 = DENIED/REJECTED, 5 = HINT, 6 = STATUS/config change
  6584.         if    ( $ev{'sdmode'} == 0 ) { $ev{'sdmode'} = "UNKNOWN"; }
  6585.         elsif ( $ev{'sdmode'} == 1 ) { $ev{'sdmode'} = "ERROR"; }
  6586.         elsif ( $ev{'sdmode'} == 2 ) { $ev{'sdmode'} = "AUDITING"; }
  6587.         elsif ( $ev{'sdmode'} == 3 ) { $ev{'sdmode'} = "PERMITTING"; }
  6588.         elsif ( $ev{'sdmode'} == 4 ) { $ev{'sdmode'} = "REJECTING"; }
  6589.         elsif ( $ev{'sdmode'} == 5 ) { $ev{'sdmode'} = "HINT"; }
  6590.         elsif ( $ev{'sdmode'} == 6 ) { $ev{'sdmode'} = "STATUS"; }
  6591.         else  { delete($ev{'sdmode'}); }
  6592.     }
  6593.     if ( $ev{sdmode} ) {
  6594.        $DEBUGGING && debug( Data::Dumper->Dump([%ev], [qw(*event)]));
  6595.        return \%ev;
  6596.     } else {
  6597.        return( undef );
  6598.     }
  6599. }
  6600.  
  6601. ###############################################################################
  6602. # required initialization
  6603.  
  6604. $cfg = read_config("logprof.conf");
  6605. if ((not defined $cfg->{settings}{default_owner_prompt})) {
  6606.     $cfg->{settings}{default_owner_prompt} = 0;
  6607. }
  6608.  
  6609. $profiledir = find_first_dir($cfg->{settings}{profiledir}) || "/etc/apparmor.d";
  6610. unless (-d $profiledir) { fatal_error "Can't find AppArmor profiles."; }
  6611.  
  6612. $extraprofiledir = find_first_dir($cfg->{settings}{inactive_profiledir}) ||
  6613. "/etc/apparmor/profiles/extras/";
  6614.  
  6615. $parser = find_first_file($cfg->{settings}{parser}) || "/sbin/apparmor_parser";
  6616. unless (-x $parser) { fatal_error "Can't find apparmor_parser."; }
  6617.  
  6618. $filename = find_first_file($cfg->{settings}{logfiles}) || "/var/log/messages";
  6619. unless (-f $filename) { fatal_error "Can't find system log."; }
  6620.  
  6621. $ldd = find_first_file($cfg->{settings}{ldd}) || "/usr/bin/ldd";
  6622. unless (-x $ldd) { fatal_error "Can't find ldd."; }
  6623.  
  6624. $logger = find_first_file($cfg->{settings}{logger}) || "/bin/logger";
  6625. unless (-x $logger) { fatal_error "Can't find logger."; }
  6626.  
  6627. 1;
  6628.  
  6629.